From 061365fd54a4d47747081dafd73bad34cc1b1297 Mon Sep 17 00:00:00 2001 From: Nicolas Le Goff Date: Tue, 4 Mar 2014 21:03:39 +0100 Subject: [PATCH 001/112] Add oauth entities --- .../Phrasea/Model/Entities/ApiAccount.php | 164 ++++++++ .../Phrasea/Model/Entities/ApiApplication.php | 381 ++++++++++++++++++ lib/Alchemy/Phrasea/Model/Entities/ApiLog.php | 325 +++++++++++++++ .../Phrasea/Model/Entities/ApiOauthCode.php | 203 ++++++++++ .../Model/Entities/ApiOauthRefreshToken.php | 176 ++++++++ .../Phrasea/Model/Entities/ApiOauthToken.php | 202 ++++++++++ 6 files changed, 1451 insertions(+) create mode 100644 lib/Alchemy/Phrasea/Model/Entities/ApiAccount.php create mode 100644 lib/Alchemy/Phrasea/Model/Entities/ApiApplication.php create mode 100644 lib/Alchemy/Phrasea/Model/Entities/ApiLog.php create mode 100644 lib/Alchemy/Phrasea/Model/Entities/ApiOauthCode.php create mode 100644 lib/Alchemy/Phrasea/Model/Entities/ApiOauthRefreshToken.php create mode 100644 lib/Alchemy/Phrasea/Model/Entities/ApiOauthToken.php diff --git a/lib/Alchemy/Phrasea/Model/Entities/ApiAccount.php b/lib/Alchemy/Phrasea/Model/Entities/ApiAccount.php new file mode 100644 index 0000000000..ff4473fbe8 --- /dev/null +++ b/lib/Alchemy/Phrasea/Model/Entities/ApiAccount.php @@ -0,0 +1,164 @@ +apiVersion = $apiVersion; + + return $this; + } + + /** + * @return string + */ + public function getApiVersion() + { + return $this->apiVersion; + } + + /** + * @param ApiApplication $application + * + * @return ApiAccount + */ + public function setApplication(ApiApplication $application) + { + $this->application = $application; + + return $this; + } + + /** + * @return ApiApplication + */ + public function getApplication() + { + return $this->application; + } + + /** + * @param \DateTime $created + * + * @return ApiAccount + */ + public function setCreated(\DateTime $created) + { + $this->created = $created; + + return $this; + } + + /** + * @return \DateTime + */ + public function getCreated() + { + return $this->created; + } + + /** + * @return integer + */ + public function getId() + { + return $this->id; + } + + /** + * @param boolean $revoked + * + * @return ApiAccount + */ + public function setRevoked($revoked) + { + $this->revoked = (Boolean) $revoked; + + return $this; + } + + /** + * @return boolean + */ + public function isRevoked() + { + return $this->revoked; + } + + /** + * @param User $user + * + * @return ApiAccount + */ + public function setUser(User $user) + { + $this->user = $user; + + return $this; + } + + /** + * @return User + */ + public function getUser() + { + return $this->user; + } +} diff --git a/lib/Alchemy/Phrasea/Model/Entities/ApiApplication.php b/lib/Alchemy/Phrasea/Model/Entities/ApiApplication.php new file mode 100644 index 0000000000..15a9415217 --- /dev/null +++ b/lib/Alchemy/Phrasea/Model/Entities/ApiApplication.php @@ -0,0 +1,381 @@ +activated = (Boolean) $activated; + + return $this; + } + + /** + * @return boolean + */ + public function isActivated() + { + return $this->activated; + } + + /** + * @param string $clientId + * + * @return ApiApplication + */ + public function setClientId($clientId) + { + $this->clientId = $clientId; + + return $this; + } + + /** + * @return string + */ + public function getClientId() + { + return $this->clientId; + } + + /** + * @param string $clientSecret + * + * @return ApiApplication + */ + public function setClientSecret($clientSecret) + { + $this->clientSecret = $clientSecret; + + return $this; + } + + /** + * @return string + */ + public function getClientSecret() + { + return $this->clientSecret; + } + + /** + * @param \DateTime $created + * + * @return ApiApplication + */ + public function setCreated(\DateTime $created) + { + $this->created = $created; + + return $this; + } + + /** + * @return \DateTime + */ + public function getCreated() + { + return $this->created; + } + + /** + * @param User $creator + * + * @return ApiApplication + */ + public function setCreator(User $creator = null) + { + $this->creator = $creator; + + return $this; + } + + /** + * @return User|null + */ + public function getCreator() + { + return $this->creator; + } + + /** + * @param string $description + * + * @return ApiApplication + */ + public function setDescription($description) + { + $this->description = $description; + + return $this; + } + + /** + * @return string + */ + public function getDescription() + { + return $this->description; + } + + /** + * @param boolean $grantPassword + * + * @return ApiApplication + */ + public function setGrantPassword($grantPassword) + { + $this->grantPassword = (Boolean) $grantPassword; + + return $this; + } + + /** + * @return boolean + */ + public function isPasswordGranted() + { + return $this->grantPassword; + } + + /** + * @return integer + */ + public function getId() + { + return $this->id; + } + + /** + * @param string $name + * + * @return ApiApplication + */ + public function setName($name) + { + $this->name = $name; + + return $this; + } + + /** + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * @param string $nonce + * + * @return ApiApplication + */ + public function setNonce($nonce) + { + $this->nonce = $nonce; + + return $this; + } + + /** + * @return string + */ + public function getNonce() + { + return $this->nonce; + } + + /** + * @param string $redirectUri + * + * @return ApiApplication + */ + public function setRedirectUri($redirectUri) + { + $this->redirectUri = $redirectUri; + + return $this; + } + + /** + * @return string + */ + public function getRedirectUri() + { + return $this->redirectUri; + } + + /** + * @param string $type + * + * @return ApiApplication + */ + public function setType($type) + { + $this->type = $type; + + return $this; + } + + /** + * @return string + */ + public function getType() + { + return $this->type; + } + + /** + * @param \DateTime $updated + * + * @return ApiApplication + */ + public function setUpdated(\DateTime $updated) + { + $this->updated = $updated; + + return $this; + } + + /** + * @return \DateTime + */ + public function getUpdated() + { + return $this->updated; + } + + /** + * @param string $website + * + * @return ApiApplication + */ + public function setWebsite($website) + { + $this->website = $website; + + return $this; + } + + /** + * @return string + */ + public function getWebsite() + { + return $this->website; + } +} diff --git a/lib/Alchemy/Phrasea/Model/Entities/ApiLog.php b/lib/Alchemy/Phrasea/Model/Entities/ApiLog.php new file mode 100644 index 0000000000..9788b86924 --- /dev/null +++ b/lib/Alchemy/Phrasea/Model/Entities/ApiLog.php @@ -0,0 +1,325 @@ +account = $account; + + return $this; + } + + /** + * @return ApiAccount + */ + public function getAccount() + { + return $this->account; + } + + /** + * @param string $action + * + * @return ApiLog + */ + public function setAction($action) + { + $this->action = $action; + + return $this; + } + + /** + * @return string + */ + public function getAction() + { + return $this->action; + } + + /** + * @param string $aspect + * + * @return ApiLog + */ + public function setAspect($aspect) + { + $this->aspect = $aspect; + + return $this; + } + + /** + * @return string + */ + public function getAspect() + { + return $this->aspect; + } + + /** + * @param integer $errorCode + * + * @return ApiLog + */ + public function setErrorCode($errorCode) + { + $this->errorCode = $errorCode; + + return $this; + } + + /** + * @return integer + */ + public function getErrorCode() + { + return $this->errorCode; + } + + /** + * @param string $errorMessage + * + * @return ApiLog + */ + public function setErrorMessage($errorMessage) + { + $this->errorMessage = $errorMessage; + + return $this; + } + + /** + * @return string + */ + public function getErrorMessage() + { + return $this->errorMessage; + } + + /** + * @param string $format + * + * @return ApiLog + */ + public function setFormat($format) + { + $this->format = $format; + + return $this; + } + + /** + * @return string + */ + public function getFormat() + { + return $this->format; + } + + /** + * @param string $general + * + * @return ApiLog + */ + public function setGeneral($general) + { + $this->general = $general; + + return $this; + } + + /** + * @return string + */ + public function getGeneral() + { + return $this->general; + } + + /** + * @param string $resource + * + * @return ApiLog + */ + public function setResource($resource) + { + $this->resource = $resource; + + return $this; + } + + /** + * @return string + */ + public function getResource() + { + return $this->resource; + } + + /** + * @param string $route + * + * @return ApiLog + */ + public function setRoute($route) + { + $this->route = $route; + + return $this; + } + + /** + * @return string + */ + public function getRoute() + { + return $this->route; + } + + /** + * @param integer $statusCode + * + * @return ApiLog + */ + public function setStatusCode($statusCode) + { + $this->statusCode = $statusCode; + + return $this; + } + + /** + * @return integer + */ + public function getStatusCode() + { + return $this->statusCode; + } + + /** + * @param \DateTime $created + * + * @return ApiLog + */ + public function setCreated(\DateTime $created) + { + $this->created = $created; + + return $this; + } + + /** + * @return \DateTime + */ + public function getCreated() + { + return $this->created; + } + + /** + * @return integer + */ + public function getId() + { + return $this->id; + } +} diff --git a/lib/Alchemy/Phrasea/Model/Entities/ApiOauthCode.php b/lib/Alchemy/Phrasea/Model/Entities/ApiOauthCode.php new file mode 100644 index 0000000000..218eadee03 --- /dev/null +++ b/lib/Alchemy/Phrasea/Model/Entities/ApiOauthCode.php @@ -0,0 +1,203 @@ +account = $account; + + return $this; + } + + /** + * @return ApiAccount + */ + public function getAccount() + { + return $this->account; + } + + /** + * @param string $code + * + * @return ApiOauthCode + */ + public function setCode($code) + { + $this->code = $code; + + return $this; + } + + /** + * @return string + */ + public function getCode() + { + return $this->code; + } + + /** + * @param \DateTime $created + * + * @return ApiOauthCode + */ + public function setCreated(\DateTime $created) + { + $this->created = $created; + + return $this; + } + + /** + * @return \DateTime + */ + public function getCreated() + { + return $this->created; + } + + /** + * @param \DateTime $expires + * + * @return ApiOauthCode + */ + public function setExpires(\DateTime $expires) + { + $this->expires = $expires; + + return $this; + } + + /** + * @return \DateTime + */ + public function getExpires() + { + return $this->expires; + } + + /** + * @param string $redirectUri + * + * @return ApiOauthCode + */ + public function setRedirectUri($redirectUri) + { + $this->redirectUri = $redirectUri; + + return $this; + } + + /** + * @return string + */ + public function getRedirectUri() + { + return $this->redirectUri; + } + + /** + * @param string $scope + * + * @return ApiOauthCode + */ + public function setScope($scope) + { + $this->scope = $scope; + + return $this; + } + + /** + * @return string + */ + public function getScope() + { + return $this->scope; + } + + /** + * @param \DateTime $updated + * + * @return ApiOauthCode + */ + public function setUpdated(\DateTime $updated) + { + $this->updated = $updated; + + return $this; + } + + /** + * @return \DateTime + */ + public function getUpdated() + { + return $this->updated; + } +} diff --git a/lib/Alchemy/Phrasea/Model/Entities/ApiOauthRefreshToken.php b/lib/Alchemy/Phrasea/Model/Entities/ApiOauthRefreshToken.php new file mode 100644 index 0000000000..e19d177b99 --- /dev/null +++ b/lib/Alchemy/Phrasea/Model/Entities/ApiOauthRefreshToken.php @@ -0,0 +1,176 @@ +account = $account; + + return $this; + } + + /** + * @return ApiAccount + */ + public function getAccount() + { + return $this->account; + } + + /** + * @param \DateTime $created + * + * @return ApiOauthRefreshToken + */ + public function setCreated(\DateTime $created) + { + $this->created = $created; + + return $this; + } + + /** + * @return \DateTime + */ + public function getCreated() + { + return $this->created; + } + + /** + * @param \DateTime $expires + * + * @return ApiOauthRefreshToken + */ + public function setExpires(\DateTime $expires) + { + $this->expires = $expires; + + return $this; + } + + /** + * @return \DateTime + */ + public function getExpires() + { + return $this->expires; + } + + /** + * @param string $refreshToken + * + * @return ApiOauthRefreshToken + */ + public function setRefreshToken($refreshToken) + { + $this->refreshToken = $refreshToken; + + return $this; + } + + /** + * @return string + */ + public function getRefreshToken() + { + return $this->refreshToken; + } + + /** + * @param string $scope + * + * @return ApiOauthRefreshToken + */ + public function setScope($scope) + { + $this->scope = $scope; + + return $this; + } + + /** + * @return string + */ + public function getScope() + { + return $this->scope; + } + + /** + * @param \DateTime $updated + * + * @return ApiOauthRefreshToken + */ + public function setUpdated(\DateTime $updated) + { + $this->updated = $updated; + + return $this; + } + + /** + * @return \DateTime + */ + public function getUpdated() + { + return $this->updated; + } +} diff --git a/lib/Alchemy/Phrasea/Model/Entities/ApiOauthToken.php b/lib/Alchemy/Phrasea/Model/Entities/ApiOauthToken.php new file mode 100644 index 0000000000..d0dd01c33f --- /dev/null +++ b/lib/Alchemy/Phrasea/Model/Entities/ApiOauthToken.php @@ -0,0 +1,202 @@ +account = $account; + + return $this; + } + + /** + * @return ApiAccount + */ + public function getAccount() + { + return $this->account; + } + + /** + * @param \DateTime $created + * + * @return ApiOauthTokens + */ + public function setCreated(\DateTime $created) + { + $this->created = $created; + + return $this; + } + + /** + * @return \DateTime + */ + public function getCreated() + { + return $this->created; + } + + /** + * @param \DateTime $expires + * + * @return ApiOauthTokens + */ + public function setExpires(\DateTime $expires) + { + $this->expires = $expires; + + return $this; + } + + /** + * @return \DateTime + */ + public function getExpires() + { + return $this->expires; + } + + /** + * @param string $oauthToken + * + * @return ApiOauthTokens + */ + public function setOauthToken($oauthToken) + { + $this->oauthToken = $oauthToken; + + return $this; + } + + /** + * @return string + */ + public function getOauthToken() + { + return $this->oauthToken; + } + + /** + * @param string $scope + * + * @return ApiOauthTokens + */ + public function setScope($scope) + { + $this->scope = $scope; + + return $this; + } + + /** + * @return string + */ + public function getScope() + { + return $this->scope; + } + + /** + * @param Session $session + * + * @return ApiOauthTokens + */ + public function setSession(Session $session) + { + $this->session = $session; + + return $this; + } + + /** + * @return Session + */ + public function getSession() + { + return $this->session; + } + + /** + * @param \DateTime $updated + * + * @return ApiOauthTokens + */ + public function setUpdated(\DateTime $updated) + { + $this->updated = $updated; + + return $this; + } + + /** + * @return \DateTime + */ + public function getUpdated() + { + return $this->updated; + } +} From 5d3cba6ce58c0067cb3592aa1abe0aa6a1318930 Mon Sep 17 00:00:00 2001 From: Nicolas Le Goff Date: Tue, 4 Mar 2014 21:03:51 +0100 Subject: [PATCH 002/112] Add oauth repositories --- .../Model/Repositories/ApiAccountRepository.php | 15 +++++++++++++++ .../Repositories/ApiApplicationRepository.php | 15 +++++++++++++++ .../Model/Repositories/ApiLogRepository.php | 15 +++++++++++++++ .../Model/Repositories/ApiOauthCodeRepository.php | 15 +++++++++++++++ .../ApiOauthRefreshTokenRepository.php | 15 +++++++++++++++ .../Repositories/ApiOauthTokenRepository.php | 15 +++++++++++++++ 6 files changed, 90 insertions(+) create mode 100644 lib/Alchemy/Phrasea/Model/Repositories/ApiAccountRepository.php create mode 100644 lib/Alchemy/Phrasea/Model/Repositories/ApiApplicationRepository.php create mode 100644 lib/Alchemy/Phrasea/Model/Repositories/ApiLogRepository.php create mode 100644 lib/Alchemy/Phrasea/Model/Repositories/ApiOauthCodeRepository.php create mode 100644 lib/Alchemy/Phrasea/Model/Repositories/ApiOauthRefreshTokenRepository.php create mode 100644 lib/Alchemy/Phrasea/Model/Repositories/ApiOauthTokenRepository.php diff --git a/lib/Alchemy/Phrasea/Model/Repositories/ApiAccountRepository.php b/lib/Alchemy/Phrasea/Model/Repositories/ApiAccountRepository.php new file mode 100644 index 0000000000..907ceb957c --- /dev/null +++ b/lib/Alchemy/Phrasea/Model/Repositories/ApiAccountRepository.php @@ -0,0 +1,15 @@ + Date: Wed, 5 Mar 2014 12:06:37 +0100 Subject: [PATCH 003/112] Fix bad index --- lib/Alchemy/Phrasea/Model/Entities/ApiAccount.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Alchemy/Phrasea/Model/Entities/ApiAccount.php b/lib/Alchemy/Phrasea/Model/Entities/ApiAccount.php index ff4473fbe8..239c3ce3d4 100644 --- a/lib/Alchemy/Phrasea/Model/Entities/ApiAccount.php +++ b/lib/Alchemy/Phrasea/Model/Entities/ApiAccount.php @@ -6,7 +6,7 @@ use Doctrine\ORM\Mapping as ORM; use Gedmo\Mapping\Annotation as Gedmo; /** - * @ORM\Table(name="ApiAccounts", indexes={@ORM\Index(name="usr_id", columns={"usr_id"}), @ORM\Index(name="application_id", columns={"application_id"})}) + * @ORM\Table(name="ApiAccounts", indexes={@ORM\Index(name="user_id", columns={"user_id"}), @ORM\Index(name="application_id", columns={"application_id"})}) * @ORM\Entity(repositoryClass="Alchemy\Phrasea\Model\Repositories\ApiAccountRepository") */ class ApiAccount From 349834fa998bc2da0d39c495c27ba6e32743d5a1 Mon Sep 17 00:00:00 2001 From: Nicolas Le Goff Date: Wed, 5 Mar 2014 12:12:48 +0100 Subject: [PATCH 004/112] Set revoked to false by default --- lib/Alchemy/Phrasea/Model/Entities/ApiAccount.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Alchemy/Phrasea/Model/Entities/ApiAccount.php b/lib/Alchemy/Phrasea/Model/Entities/ApiAccount.php index 239c3ce3d4..7fea557344 100644 --- a/lib/Alchemy/Phrasea/Model/Entities/ApiAccount.php +++ b/lib/Alchemy/Phrasea/Model/Entities/ApiAccount.php @@ -31,7 +31,7 @@ class ApiAccount * * @ORM\Column(type="boolean") */ - private $revoked; + private $revoked = false; /** * @var string From 6d334f1cf84babe0b3587ae93965701c2b504eeb Mon Sep 17 00:00:00 2001 From: Nicolas Le Goff Date: Wed, 5 Mar 2014 12:51:53 +0100 Subject: [PATCH 005/112] Add web, desktop & URN constant for API application --- lib/Alchemy/Phrasea/Model/Entities/ApiApplication.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/Alchemy/Phrasea/Model/Entities/ApiApplication.php b/lib/Alchemy/Phrasea/Model/Entities/ApiApplication.php index 15a9415217..c65e03fca0 100644 --- a/lib/Alchemy/Phrasea/Model/Entities/ApiApplication.php +++ b/lib/Alchemy/Phrasea/Model/Entities/ApiApplication.php @@ -14,6 +14,13 @@ use Gedmo\Mapping\Annotation as Gedmo; */ class ApiApplication { + /** desktop application */ + const DESKTOP_TYPE = 'desktop'; + /** web application */ + const WEB_TYPE = 'web'; + /** Uniform Resource Name */ + const NATIVE_APP_REDIRECT_URI = "urn:ietf:wg:oauth:2.0:oob"; + /** * @ORM\Column(type="integer") * @ORM\Id From 2ed0ce1f9dec2214f686b3ae663def8e2e6d0995 Mon Sep 17 00:00:00 2001 From: Nicolas Le Goff Date: Wed, 5 Mar 2014 13:23:18 +0100 Subject: [PATCH 006/112] Set default value for grantedPassword & activated properties --- lib/Alchemy/Phrasea/Model/Entities/ApiApplication.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/Alchemy/Phrasea/Model/Entities/ApiApplication.php b/lib/Alchemy/Phrasea/Model/Entities/ApiApplication.php index c65e03fca0..13726edf18 100644 --- a/lib/Alchemy/Phrasea/Model/Entities/ApiApplication.php +++ b/lib/Alchemy/Phrasea/Model/Entities/ApiApplication.php @@ -109,14 +109,14 @@ class ApiApplication * * @ORM\Column(type="boolean", nullable=false) */ - private $activated; + private $activated = true; /** * @var integer * * @ORM\Column(name="grant_password", type="boolean", nullable=false) */ - private $grantPassword; + private $grantPassword = false; /** * @param boolean $activated From d3205e6f6ae2e9989600050ad53f802fd26fd149 Mon Sep 17 00:00:00 2001 From: Nicolas Le Goff Date: Wed, 5 Mar 2014 13:29:50 +0100 Subject: [PATCH 007/112] Add Bi-Directional relationship between ApiApplication & ApiAccount --- lib/Alchemy/Phrasea/Model/Entities/ApiAccount.php | 2 +- lib/Alchemy/Phrasea/Model/Entities/ApiApplication.php | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/Alchemy/Phrasea/Model/Entities/ApiAccount.php b/lib/Alchemy/Phrasea/Model/Entities/ApiAccount.php index 7fea557344..a942242e39 100644 --- a/lib/Alchemy/Phrasea/Model/Entities/ApiAccount.php +++ b/lib/Alchemy/Phrasea/Model/Entities/ApiAccount.php @@ -41,7 +41,7 @@ class ApiAccount private $apiVersion; /** - * @ORM\ManyToOne(targetEntity="ApiApplication") + * @ORM\ManyToOne(targetEntity="ApiApplication", inversedBy="accounts") * @ORM\JoinColumn(name="application_id", referencedColumnName="id", nullable=false) * * @return ApiApplication diff --git a/lib/Alchemy/Phrasea/Model/Entities/ApiApplication.php b/lib/Alchemy/Phrasea/Model/Entities/ApiApplication.php index 13726edf18..cdeeed2866 100644 --- a/lib/Alchemy/Phrasea/Model/Entities/ApiApplication.php +++ b/lib/Alchemy/Phrasea/Model/Entities/ApiApplication.php @@ -118,6 +118,12 @@ class ApiApplication */ private $grantPassword = false; + + /** + * @OneToMany(targetEntity="ApiAccount", mappedBy="product") + **/ + private $accounts; + /** * @param boolean $activated * From 60474d618c24706f1c6822c154ed1c90b718e7f5 Mon Sep 17 00:00:00 2001 From: Nicolas Le Goff Date: Wed, 5 Mar 2014 13:30:42 +0100 Subject: [PATCH 008/112] Add cascade remove for accounts relationship, when deleting an application related accounts will be deleted as well --- lib/Alchemy/Phrasea/Model/Entities/ApiApplication.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Alchemy/Phrasea/Model/Entities/ApiApplication.php b/lib/Alchemy/Phrasea/Model/Entities/ApiApplication.php index cdeeed2866..3460ea3325 100644 --- a/lib/Alchemy/Phrasea/Model/Entities/ApiApplication.php +++ b/lib/Alchemy/Phrasea/Model/Entities/ApiApplication.php @@ -120,7 +120,7 @@ class ApiApplication /** - * @OneToMany(targetEntity="ApiAccount", mappedBy="product") + * @OneToMany(targetEntity="ApiAccount", mappedBy="product", cascade={"remove"}) **/ private $accounts; From 0d4bd6ef305c45468cc822a61b869acce7a24413 Mon Sep 17 00:00:00 2001 From: Nicolas Le Goff Date: Wed, 5 Mar 2014 13:32:41 +0100 Subject: [PATCH 009/112] Add accounts property getter --- lib/Alchemy/Phrasea/Model/Entities/ApiApplication.php | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/lib/Alchemy/Phrasea/Model/Entities/ApiApplication.php b/lib/Alchemy/Phrasea/Model/Entities/ApiApplication.php index 3460ea3325..c8d08d70a9 100644 --- a/lib/Alchemy/Phrasea/Model/Entities/ApiApplication.php +++ b/lib/Alchemy/Phrasea/Model/Entities/ApiApplication.php @@ -2,6 +2,7 @@ namespace Alchemy\Phrasea\Model\Entities; +use Doctrine\Common\Collections\ArrayCollection; use Doctrine\ORM\Mapping as ORM; use Gedmo\Mapping\Annotation as Gedmo; @@ -391,4 +392,12 @@ class ApiApplication { return $this->website; } + + /** + * @return ArrayCollection + */ + public function getAccounts() + { + return $this->accounts; + } } From ce90b7c3bfcb23653d6dbd6c9c3e2664f7385cd5 Mon Sep 17 00:00:00 2001 From: Nicolas Le Goff Date: Wed, 5 Mar 2014 13:37:44 +0100 Subject: [PATCH 010/112] Sets clientId & clientSecret field length to 32 --- lib/Alchemy/Phrasea/Model/Entities/ApiApplication.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/Alchemy/Phrasea/Model/Entities/ApiApplication.php b/lib/Alchemy/Phrasea/Model/Entities/ApiApplication.php index c8d08d70a9..d0a35787b4 100644 --- a/lib/Alchemy/Phrasea/Model/Entities/ApiApplication.php +++ b/lib/Alchemy/Phrasea/Model/Entities/ApiApplication.php @@ -80,14 +80,14 @@ class ApiApplication /** * @var string * - * @ORM\Column(name="client_id", type="string", length=128, nullable=false) + * @ORM\Column(name="client_id", type="string", length=32, nullable=false) */ private $clientId; /** * @var string * - * @ORM\Column(name="client_secret", type="string", length=128, nullable=false) + * @ORM\Column(name="client_secret", type="string", length=32, nullable=false) */ private $clientSecret; From e99b49d191305c33a93f769ada912daa1fe27260 Mon Sep 17 00:00:00 2001 From: Nicolas Le Goff Date: Wed, 5 Mar 2014 14:46:18 +0100 Subject: [PATCH 011/112] Add Account Manipulator --- .../Manipulator/ApiAccountManipulator.php | 71 +++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 lib/Alchemy/Phrasea/Model/Manipulator/ApiAccountManipulator.php diff --git a/lib/Alchemy/Phrasea/Model/Manipulator/ApiAccountManipulator.php b/lib/Alchemy/Phrasea/Model/Manipulator/ApiAccountManipulator.php new file mode 100644 index 0000000000..7fe92d7921 --- /dev/null +++ b/lib/Alchemy/Phrasea/Model/Manipulator/ApiAccountManipulator.php @@ -0,0 +1,71 @@ +om = $om; + $this->repository = $repo; + } + + public function create(ApiApplication $application, User $user = null) + { + $account = new ApiAccount(); + $account->setUser($user); + $account->setApplication($application); + + $this->update($account); + + return $account; + } + + public function delete(ApiAccount $account) + { + $this->om->remove($account); + $this->om->flush(); + } + + public function update(ApiAccount $account) + { + $this->om->persist($account); + $this->om->flush(); + } + + public function authorizeAccess(ApiAccount $account) + { + $account->setRevoked(false); + + $this->update($account); + } + + public function revokeAccess(ApiAccount $account) + { + $account->setRevoked(true); + + $this->update($account); + } +} From c5d803ac26540060fbe9ac349a8f1bb83915042a Mon Sep 17 00:00:00 2001 From: Nicolas Le Goff Date: Wed, 5 Mar 2014 14:46:39 +0100 Subject: [PATCH 012/112] Add Application Manipulator --- .../Manipulator/ApiApplicationManipulator.php | 118 ++++++++++++++++++ 1 file changed, 118 insertions(+) create mode 100644 lib/Alchemy/Phrasea/Model/Manipulator/ApiApplicationManipulator.php diff --git a/lib/Alchemy/Phrasea/Model/Manipulator/ApiApplicationManipulator.php b/lib/Alchemy/Phrasea/Model/Manipulator/ApiApplicationManipulator.php new file mode 100644 index 0000000000..dfa84f3349 --- /dev/null +++ b/lib/Alchemy/Phrasea/Model/Manipulator/ApiApplicationManipulator.php @@ -0,0 +1,118 @@ +om = $om; + $this->repository = $repo; + $this->randomGenerator = $random; + } + + public function create($name, $type, $description, $applicationWebsite, User $creator = null, $redirectUri = null) + { + $application = new ApiApplication(); + $application->setCreator($creator); + $application->setName($name); + $application->setDescription($description); + $this->doSetType($application, $type); + $this->doSetWebsiteUrl($application, $applicationWebsite); + $this->doSetRedirectUri($application, $redirectUri); + $application->setNonce($this->randomGenerator->generateString(64)); + $application->setClientId($this->randomGenerator->generateString(32, \random::LETTERS_AND_NUMBERS)); + $application->setClientSecret($this->randomGenerator->generateString(32, \random::LETTERS_AND_NUMBERS)); + + $this->om->persist($application); + $this->om->flush(); + + return $application; + } + + public function delete(ApiApplication $application) + { + $this->om->remove($application); + $this->om->flush(); + } + + public function update(ApiApplication $application) + { + $this->om->persist($application); + $this->om->flush(); + } + + public function setType(ApiApplication $application, $type) + { + $this->doSetType($application, $type); + $this->update($application); + } + + public function setRedirectUri(ApiApplication $application, $uri) + { + $this->doSetRedirectUri($application, $uri); + $this->update($application); + } + + public function setWebsiteUrl(ApiApplication $application, $url) + { + $this->doSetWebsiteUrl($application, $url); + $this->update($application); + } + + private function doSetType(ApiApplication $application, $type) + { + if (!in_array($type, [ApiApplication::DESKTOP_TYPE, ApiApplication::WEB_TYPE])) { + throw new InvalidArgumentException(sprintf('%s api application type is not supported, it should be one of the following %s', $type, implode(', ', [ApiApplication::DESKTOP_TYPE, ApiApplication::WEB_TYPE]))); + } + $application->setType($type); + } + + private function doSetRedirectUri(ApiApplication $application, $uri) + { + if ($this->type === ApiApplication::DESKTOP_TYPE) { + $application->setRedirectUri(self::NATIVE_APP_REDIRECT_URI); + + return; + } + + if (false === filter_var($uri, FILTER_VALIDATE_URL, FILTER_FLAG_SCHEME_REQUIRED | FILTER_FLAG_HOST_REQUIRED)) { + throw new InvalidArgumentException(sprintf('Url %s is not legal.', $uri)); + } + + $application->setRedirectUri($uri); + } + + private function doSetWebsiteUrl(ApiApplication $application, $url) + { + if (false === filter_var($url, FILTER_VALIDATE_URL, FILTER_FLAG_SCHEME_REQUIRED | FILTER_FLAG_HOST_REQUIRED)) { + throw new InvalidArgumentException(sprintf('Url %s is not legal.', $url)); + } + + $application->setWebsite($url); + } +} From bbbc8f7e0268f15ebde3ca0e21cfe39c7892ca23 Mon Sep 17 00:00:00 2001 From: Nicolas Le Goff Date: Wed, 5 Mar 2014 14:47:29 +0100 Subject: [PATCH 013/112] Add some application repository function --- .../Repositories/ApiApplicationRepository.php | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/lib/Alchemy/Phrasea/Model/Repositories/ApiApplicationRepository.php b/lib/Alchemy/Phrasea/Model/Repositories/ApiApplicationRepository.php index 3e622dd792..792a7b102f 100644 --- a/lib/Alchemy/Phrasea/Model/Repositories/ApiApplicationRepository.php +++ b/lib/Alchemy/Phrasea/Model/Repositories/ApiApplicationRepository.php @@ -2,7 +2,10 @@ namespace Alchemy\Phrasea\Model\Repositories; +use Alchemy\Phrasea\Model\Entities\User; +use Doctrine\Common\Collections\ArrayCollection; use Doctrine\ORM\EntityRepository; +use Doctrine\ORM\Query\Expr; /** * ApiApplicationRepository @@ -12,4 +15,40 @@ use Doctrine\ORM\EntityRepository; */ class ApiApplicationRepository extends EntityRepository { + public function findByClientId($clientId) + { + $qb = $this->createQueryBuilder('app'); + $qb->where($qb->expr()->eq('app.clientId', ':clientId')); + $qb->setParameter(':clientId', $clientId); + + return $qb->getQuery()->getSingleResult(); + } + + public function findByCreator(User $user) + { + $qb = $this->createQueryBuilder('app'); + $qb->where($qb->expr()->eq('app.creator', ':creator')); + $qb->setParameter(':creator', $user); + + return $qb->getQuery()->getResult(); + } + + public function findByUser(User $user) + { + $qb = $this->createQueryBuilder('app'); + $qb->innerJoin('app.accounts', 'acc', Expr\Join::WITH, $qb->expr()->eq('acc.user', ':user')); + $qb->setParameter(':user', $user); + + return $qb->getQuery()->getResult(); + } + + public function findAuthorizedAppsByUser(User $user) + { + $qb = $this->createQueryBuilder('app'); + $qb->innerJoin('app.accounts', 'acc', Expr\Join::WITH, $qb->expr()->eq('acc.user', ':user')); + $qb->andWhere($qb->expr()->eq('acc.revoked', $qb->expr()->literal(false))); + $qb->setParameter(':user', $user); + + return $qb->getQuery()->getResult(); + } } From c477c254e686a0b128c3ca13f8ddf478cecc544e Mon Sep 17 00:00:00 2001 From: Nicolas Le Goff Date: Wed, 5 Mar 2014 14:51:14 +0100 Subject: [PATCH 014/112] Fix use statement --- .../Phrasea/Model/Manipulator/ApiAccountManipulator.php | 2 -- .../Phrasea/Model/Manipulator/ApiApplicationManipulator.php | 4 +--- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/lib/Alchemy/Phrasea/Model/Manipulator/ApiAccountManipulator.php b/lib/Alchemy/Phrasea/Model/Manipulator/ApiAccountManipulator.php index 7fe92d7921..8382c1960d 100644 --- a/lib/Alchemy/Phrasea/Model/Manipulator/ApiAccountManipulator.php +++ b/lib/Alchemy/Phrasea/Model/Manipulator/ApiAccountManipulator.php @@ -15,10 +15,8 @@ use Alchemy\Phrasea\Application; use Alchemy\Phrasea\Authentication\ACLProvider; use Alchemy\Phrasea\Model\Entities\ApiAccount; use Alchemy\Phrasea\Model\Entities\ApiApplication; -use Alchemy\Phrasea\Model\Entities\Registration; use Alchemy\Phrasea\Model\Entities\User; use Doctrine\Common\Persistence\ObjectManager; -use Doctrine\ORM\EntityManager; use Doctrine\ORM\EntityRepository; class ApiAccountManipulator implements ManipulatorInterface diff --git a/lib/Alchemy/Phrasea/Model/Manipulator/ApiApplicationManipulator.php b/lib/Alchemy/Phrasea/Model/Manipulator/ApiApplicationManipulator.php index dfa84f3349..f7b0c4b3d8 100644 --- a/lib/Alchemy/Phrasea/Model/Manipulator/ApiApplicationManipulator.php +++ b/lib/Alchemy/Phrasea/Model/Manipulator/ApiApplicationManipulator.php @@ -14,13 +14,11 @@ namespace Alchemy\Phrasea\Model\Manipulator; use Alchemy\Phrasea\Application; use Alchemy\Phrasea\Authentication\ACLProvider; use Alchemy\Phrasea\Exception\InvalidArgumentException; -use Alchemy\Phrasea\Model\Entities\ApiAccount; use Alchemy\Phrasea\Model\Entities\ApiApplication; -use Alchemy\Phrasea\Model\Entities\Registration; use Alchemy\Phrasea\Model\Entities\User; use Doctrine\Common\Persistence\ObjectManager; -use Doctrine\ORM\EntityManager; use Doctrine\ORM\EntityRepository; +use RandomLib\Generator; class ApiApplicationManipulator implements ManipulatorInterface { From feca23e1c66377e8a657d6181c73eedd5aff0de3 Mon Sep 17 00:00:00 2001 From: Nicolas Le Goff Date: Wed, 5 Mar 2014 15:06:07 +0100 Subject: [PATCH 015/112] Add OauthToken manipulator --- .../Manipulator/ApiOauthTokenManipulator.php | 79 +++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 lib/Alchemy/Phrasea/Model/Manipulator/ApiOauthTokenManipulator.php diff --git a/lib/Alchemy/Phrasea/Model/Manipulator/ApiOauthTokenManipulator.php b/lib/Alchemy/Phrasea/Model/Manipulator/ApiOauthTokenManipulator.php new file mode 100644 index 0000000000..62ff2610cf --- /dev/null +++ b/lib/Alchemy/Phrasea/Model/Manipulator/ApiOauthTokenManipulator.php @@ -0,0 +1,79 @@ +om = $om; + $this->repository = $repo; + $this->randomGenerator = $random; + } + + public function create(ApiAccount $account, Session $session = null , \DateTime $expire = null, $scope = null) + { + $token = new ApiOauthToken(); + $token->setOauthToken($this->getNewToken()); + $token->setExpires($expire); + $token->setScope($scope); + $token->setAccount($account); + $token->setSession($session); + + $this->update($token); + + return $token; + } + + public function delete(ApiOauthToken $token) + { + $this->om->remove($token); + $this->om->flush(); + } + + public function update(ApiOauthToken $token) + { + $this->om->persist($token); + $this->om->flush(); + } + + public function renew(ApiOauthToken $token, \DateTime $expire = null) + { + $token->setOauthToken($this->getNewToken()); + $token->setExpires($expire); + + $this->update($token); + } + + private function getNewToken() + { + do { + $token = $this->randomGenerator->generateString(32, \random::LETTERS_AND_NUMBERS); + } while (null !== $this->repository->find($token)); + + return $token; + } +} From a9c0c3a78785a527c736f3d52130870bef816657 Mon Sep 17 00:00:00 2001 From: Nicolas Le Goff Date: Wed, 5 Mar 2014 15:12:26 +0100 Subject: [PATCH 016/112] Set code length to 16 --- lib/Alchemy/Phrasea/Model/Entities/ApiOauthCode.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Alchemy/Phrasea/Model/Entities/ApiOauthCode.php b/lib/Alchemy/Phrasea/Model/Entities/ApiOauthCode.php index 218eadee03..639081fda0 100644 --- a/lib/Alchemy/Phrasea/Model/Entities/ApiOauthCode.php +++ b/lib/Alchemy/Phrasea/Model/Entities/ApiOauthCode.php @@ -14,7 +14,7 @@ class ApiOauthCode /** * @var string * - * @ORM\Column(name="code", type="string", length=128, nullable=false) + * @ORM\Column(name="code", type="string", length=16, nullable=false) * @ORM\Id * @ORM\GeneratedValue(strategy="IDENTITY") */ From 5296601d62d4781a24cf2607503d691c30839db8 Mon Sep 17 00:00:00 2001 From: Nicolas Le Goff Date: Wed, 5 Mar 2014 15:15:24 +0100 Subject: [PATCH 017/112] Set scope length to 128 --- lib/Alchemy/Phrasea/Model/Entities/ApiOauthCode.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Alchemy/Phrasea/Model/Entities/ApiOauthCode.php b/lib/Alchemy/Phrasea/Model/Entities/ApiOauthCode.php index 639081fda0..4cbe575b20 100644 --- a/lib/Alchemy/Phrasea/Model/Entities/ApiOauthCode.php +++ b/lib/Alchemy/Phrasea/Model/Entities/ApiOauthCode.php @@ -45,7 +45,7 @@ class ApiOauthCode /** * @var string * - * @ORM\Column(type="string", length=200, nullable=true) + * @ORM\Column(type="string", length=128, nullable=true) */ private $scope; From d6a5a7922955ac6f3214d19dfa6c19cba38dd008 Mon Sep 17 00:00:00 2001 From: Nicolas Le Goff Date: Wed, 5 Mar 2014 15:24:21 +0100 Subject: [PATCH 018/112] Add some OauthCode repository functions --- .../Model/Repositories/ApiOauthCodeRepository.php | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/lib/Alchemy/Phrasea/Model/Repositories/ApiOauthCodeRepository.php b/lib/Alchemy/Phrasea/Model/Repositories/ApiOauthCodeRepository.php index 850889860e..de1399f460 100644 --- a/lib/Alchemy/Phrasea/Model/Repositories/ApiOauthCodeRepository.php +++ b/lib/Alchemy/Phrasea/Model/Repositories/ApiOauthCodeRepository.php @@ -2,6 +2,7 @@ namespace Alchemy\Phrasea\Model\Repositories; +use Alchemy\Phrasea\Model\Entities\ApiAccount; use Doctrine\ORM\EntityRepository; /** @@ -12,4 +13,12 @@ use Doctrine\ORM\EntityRepository; */ class ApiOauthCodeRepository extends EntityRepository { + public function findByAccount(ApiAccount $account) + { + $qb = $this->createQueryBuilder('c'); + $qb->where($qb->expr()->eq('c.account', ':account')); + $qb->setParameter(':account', $account); + + return $qb->getQuery()->getResult(); + } } From 536e54fed766a2e5e0efc3092a9a995f903d4c33 Mon Sep 17 00:00:00 2001 From: Nicolas Le Goff Date: Wed, 5 Mar 2014 15:25:21 +0100 Subject: [PATCH 019/112] Add ApiOauthCode Manipulator --- .../Manipulator/ApiOauthCodeManipulator.php | 71 +++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 lib/Alchemy/Phrasea/Model/Manipulator/ApiOauthCodeManipulator.php diff --git a/lib/Alchemy/Phrasea/Model/Manipulator/ApiOauthCodeManipulator.php b/lib/Alchemy/Phrasea/Model/Manipulator/ApiOauthCodeManipulator.php new file mode 100644 index 0000000000..6ac03007c8 --- /dev/null +++ b/lib/Alchemy/Phrasea/Model/Manipulator/ApiOauthCodeManipulator.php @@ -0,0 +1,71 @@ +om = $om; + $this->repository = $repo; + $this->randomGenerator = $random; + } + + public function create(ApiAccount $account, $redirectUri, \DateTime $expire = null, $scope = null) + { + $code = new ApiOauthCode(); + + $code->setCode($this->getNewCode()); + $code->setRedirectUri($redirectUri); + $code->setAccount($account); + $code->setExpires($expire); + $code->setScope($scope); + + $this->update($code); + + return $code; + } + + public function delete(ApiOauthCode $code) + { + $this->om->remove($code); + $this->om->flush(); + } + + public function update(ApiOauthCode $code) + { + $this->om->persist($code); + $this->om->flush(); + } + + private function getNewCode() + { + do { + $code = $this->randomGenerator->generateString(16, \random::LETTERS_AND_NUMBERS); + } while (null !== $this->repository->find($code)); + + return $code; + } +} From 3630b942b632c357aa6dd739e16feeedcaff94dd Mon Sep 17 00:00:00 2001 From: Nicolas Le Goff Date: Wed, 5 Mar 2014 15:29:34 +0100 Subject: [PATCH 020/112] Set oauthToken field length to 32 --- lib/Alchemy/Phrasea/Model/Entities/ApiOauthToken.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Alchemy/Phrasea/Model/Entities/ApiOauthToken.php b/lib/Alchemy/Phrasea/Model/Entities/ApiOauthToken.php index d0dd01c33f..93603c907d 100644 --- a/lib/Alchemy/Phrasea/Model/Entities/ApiOauthToken.php +++ b/lib/Alchemy/Phrasea/Model/Entities/ApiOauthToken.php @@ -14,7 +14,7 @@ class ApiOauthToken /** * @var string * - * @ORM\Column(name="oauth_token", type="string", length=128, nullable=false) + * @ORM\Column(name="oauth_token", type="string", length=32, nullable=false) * @ORM\Id * @ORM\GeneratedValue(strategy="IDENTITY") */ From 56f417a03f479f609ba4d2a4cfac397f0eaf61e3 Mon Sep 17 00:00:00 2001 From: Nicolas Le Goff Date: Wed, 5 Mar 2014 15:31:24 +0100 Subject: [PATCH 021/112] Add ApiOauthRefreshToken Manipulator --- .../ApiOauthRefreshTokenManipulator.php | 70 +++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 lib/Alchemy/Phrasea/Model/Manipulator/ApiOauthRefreshTokenManipulator.php diff --git a/lib/Alchemy/Phrasea/Model/Manipulator/ApiOauthRefreshTokenManipulator.php b/lib/Alchemy/Phrasea/Model/Manipulator/ApiOauthRefreshTokenManipulator.php new file mode 100644 index 0000000000..c7e469bcd2 --- /dev/null +++ b/lib/Alchemy/Phrasea/Model/Manipulator/ApiOauthRefreshTokenManipulator.php @@ -0,0 +1,70 @@ +om = $om; + $this->repository = $repo; + $this->randomGenerator = $random; + } + + public function create(ApiAccount $account, \DateTime $expire, $scope = null) + { + $refreshToken = new ApiOauthRefreshtoken(); + + $refreshToken->setCode($this->getNewToken()); + $refreshToken->setAccount($account); + $refreshToken->setExpires($expire); + $refreshToken->setScope($scope); + + $this->update($refreshToken); + + return $refreshToken; + } + + public function delete(ApiOauthRefreshtoken $refreshToken) + { + $this->om->remove($refreshToken); + $this->om->flush(); + } + + public function update(ApiOauthRefreshtoken $refreshToken) + { + $this->om->persist($refreshToken); + $this->om->flush(); + } + + private function getNewToken() + { + do { + $refreshToken = $this->randomGenerator->generateString(32, \random::LETTERS_AND_NUMBERS); + } while (null !== $this->repository->find($refreshToken)); + + return $refreshToken; + } +} From c065340046d844b7eff1f64324b3aea5234cb302 Mon Sep 17 00:00:00 2001 From: Nicolas Le Goff Date: Wed, 5 Mar 2014 15:32:26 +0100 Subject: [PATCH 022/112] Add ApiOauthRefreshToken Repository --- .../Model/Repositories/ApiOauthRefreshTokenRepository.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/Alchemy/Phrasea/Model/Repositories/ApiOauthRefreshTokenRepository.php b/lib/Alchemy/Phrasea/Model/Repositories/ApiOauthRefreshTokenRepository.php index 1866703639..91b4dcd5b3 100644 --- a/lib/Alchemy/Phrasea/Model/Repositories/ApiOauthRefreshTokenRepository.php +++ b/lib/Alchemy/Phrasea/Model/Repositories/ApiOauthRefreshTokenRepository.php @@ -12,4 +12,12 @@ use Doctrine\ORM\EntityRepository; */ class ApiOauthRefreshTokenRepository extends EntityRepository { + public function findByAccount(ApiAccount $account) + { + $qb = $this->createQueryBuilder('rt'); + $qb->where($qb->expr()->eq('rt.account', ':account')); + $qb->setParameter(':account', $account); + + return $qb->getQuery()->getResult(); + } } From dd5ea54468e8f7af086fd297c1190c324fd94fff Mon Sep 17 00:00:00 2001 From: Nicolas Le Goff Date: Wed, 5 Mar 2014 15:54:26 +0100 Subject: [PATCH 023/112] Register repositories as services --- .../Core/Provider/RepositoriesServiceProvider.php | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/lib/Alchemy/Phrasea/Core/Provider/RepositoriesServiceProvider.php b/lib/Alchemy/Phrasea/Core/Provider/RepositoriesServiceProvider.php index 66124296a6..355cf4d0bc 100644 --- a/lib/Alchemy/Phrasea/Core/Provider/RepositoriesServiceProvider.php +++ b/lib/Alchemy/Phrasea/Core/Provider/RepositoriesServiceProvider.php @@ -97,6 +97,21 @@ class RepositoriesServiceProvider implements ServiceProviderInterface $app['repo.presets'] = $app->share(function (PhraseaApplication $app) { return $app['EM']->getRepository('Phraseanet:Preset'); }); + $app['repo.api-accounts'] = $app->share(function (PhraseaApplication $app) { + return $app['EM']->getRepository('Phraseanet:ApiAccount'); + }); + $app['repo.api-applications'] = $app->share(function (PhraseaApplication $app) { + return $app['EM']->getRepository('Phraseanet:ApiApplication'); + }); + $app['repo.api-oauth-codes'] = $app->share(function (PhraseaApplication $app) { + return $app['EM']->getRepository('Phraseanet:ApiOauthCode'); + }); + $app['repo.api-oauth-tokens'] = $app->share(function (PhraseaApplication $app) { + return $app['EM']->getRepository('Phraseanet:ApiOauthToken'); + }); + $app['repo.api-oauth-refresh-tokens'] = $app->share(function (PhraseaApplication $app) { + return $app['EM']->getRepository('Phraseanet:ApiOauthRefreshToken'); + }); } public function boot(Application $app) From 40756ead098bc87a003ed2e2ca4d30e384785070 Mon Sep 17 00:00:00 2001 From: Nicolas Le Goff Date: Wed, 5 Mar 2014 15:54:39 +0100 Subject: [PATCH 024/112] Register manipulators as services --- .../Provider/ManipulatorServiceProvider.php | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/lib/Alchemy/Phrasea/Core/Provider/ManipulatorServiceProvider.php b/lib/Alchemy/Phrasea/Core/Provider/ManipulatorServiceProvider.php index ef34de65c4..39e757e70b 100644 --- a/lib/Alchemy/Phrasea/Core/Provider/ManipulatorServiceProvider.php +++ b/lib/Alchemy/Phrasea/Core/Provider/ManipulatorServiceProvider.php @@ -13,6 +13,11 @@ namespace Alchemy\Phrasea\Core\Provider; use Alchemy\Phrasea\Model\Manipulator\ACLManipulator; use Alchemy\Phrasea\Model\Manipulator\PresetManipulator; +use Alchemy\Phrasea\Model\Manipulator\ApiAccountManipulator; +use Alchemy\Phrasea\Model\Manipulator\ApiApplicationManipulator; +use Alchemy\Phrasea\Model\Manipulator\ApiOauthCodeManipulator; +use Alchemy\Phrasea\Model\Manipulator\ApiOauthRefreshTokenManipulator; +use Alchemy\Phrasea\Model\Manipulator\ApiOauthTokenManipulator; use Alchemy\Phrasea\Model\Manipulator\RegistrationManipulator; use Alchemy\Phrasea\Model\Manipulator\TaskManipulator; use Alchemy\Phrasea\Model\Manipulator\TokenManipulator; @@ -52,6 +57,26 @@ class ManipulatorServiceProvider implements ServiceProviderInterface $app['manipulator.registration'] = $app->share(function ($app) { return new RegistrationManipulator($app, $app['EM'], $app['acl'], $app['phraseanet.appbox'], $app['repo.registrations']); }); + + $app['manipulator.api-application'] = $app->share(function ($app) { + return new ApiApplicationManipulator($app['EM'], $app['repo.api-applications'], $app['random.medium']); + }); + + $app['manipulator.api-account'] = $app->share(function ($app) { + return new ApiAccountManipulator($app['EM'], $app['repo.api-accounts']); + }); + + $app['manipulator.api-oauth-code'] = $app->share(function ($app) { + return new ApiOauthCodeManipulator($app['EM'], $app['repo.api-oauth-codes'], $app['random.medium']); + }); + + $app['manipulator.api-oauth-token'] = $app->share(function ($app) { + return new ApiOauthTokenManipulator($app['EM'], $app['repo.api-oauth-tokens'], $app['random.medium']); + }); + + $app['manipulator.api-oauth-refresh-token'] = $app->share(function ($app) { + return new ApiOauthRefreshTokenManipulator($app['EM'], $app['repo.api-oauth-refresh-tokens'], $app['random.medium']); + }); } public function boot(SilexApplication $app) From a11aa87d0a9ba8beec6d779c3fe4b2c52cc5c697 Mon Sep 17 00:00:00 2001 From: Nicolas Le Goff Date: Wed, 5 Mar 2014 16:18:47 +0100 Subject: [PATCH 025/112] Return null if application is not found instead of throwing an exception --- .../Phrasea/Model/Repositories/ApiApplicationRepository.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Alchemy/Phrasea/Model/Repositories/ApiApplicationRepository.php b/lib/Alchemy/Phrasea/Model/Repositories/ApiApplicationRepository.php index 792a7b102f..8848db0c30 100644 --- a/lib/Alchemy/Phrasea/Model/Repositories/ApiApplicationRepository.php +++ b/lib/Alchemy/Phrasea/Model/Repositories/ApiApplicationRepository.php @@ -21,7 +21,7 @@ class ApiApplicationRepository extends EntityRepository $qb->where($qb->expr()->eq('app.clientId', ':clientId')); $qb->setParameter(':clientId', $clientId); - return $qb->getQuery()->getSingleResult(); + return $qb->getQuery()->getOneOrNullResult(); } public function findByCreator(User $user) From 9529581f41ecb4865d1376f1f1b4872c8ffd3f92 Mon Sep 17 00:00:00 2001 From: Nicolas Le Goff Date: Wed, 5 Mar 2014 16:41:14 +0100 Subject: [PATCH 026/112] Add setOauthToken method --- .../Phrasea/Model/Manipulator/ApiOauthTokenManipulator.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/Alchemy/Phrasea/Model/Manipulator/ApiOauthTokenManipulator.php b/lib/Alchemy/Phrasea/Model/Manipulator/ApiOauthTokenManipulator.php index 62ff2610cf..0ea4be9d3a 100644 --- a/lib/Alchemy/Phrasea/Model/Manipulator/ApiOauthTokenManipulator.php +++ b/lib/Alchemy/Phrasea/Model/Manipulator/ApiOauthTokenManipulator.php @@ -68,6 +68,12 @@ class ApiOauthTokenManipulator implements ManipulatorInterface $this->update($token); } + public function setOauthToken(ApiOauthToken $token, $oauthToken) + { + $token->setOauthToken($oauthToken); + $this->update($token); + } + private function getNewToken() { do { From 8744227409b6d3a82dbe430fb60b7a2705fdab57 Mon Sep 17 00:00:00 2001 From: Nicolas Le Goff Date: Wed, 5 Mar 2014 17:04:11 +0100 Subject: [PATCH 027/112] Add setCode method --- .../Phrasea/Model/Manipulator/ApiOauthCodeManipulator.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/Alchemy/Phrasea/Model/Manipulator/ApiOauthCodeManipulator.php b/lib/Alchemy/Phrasea/Model/Manipulator/ApiOauthCodeManipulator.php index 6ac03007c8..fcc89a4ab3 100644 --- a/lib/Alchemy/Phrasea/Model/Manipulator/ApiOauthCodeManipulator.php +++ b/lib/Alchemy/Phrasea/Model/Manipulator/ApiOauthCodeManipulator.php @@ -60,6 +60,12 @@ class ApiOauthCodeManipulator implements ManipulatorInterface $this->om->flush(); } + public function setCode(ApiOauthCode $code, $oauthCode) + { + $code->setCode($oauthCode); + $this->update($code); + } + private function getNewCode() { do { From f792b0051f1e70b4bac6c4418151bb32431cb0b1 Mon Sep 17 00:00:00 2001 From: Nicolas Le Goff Date: Wed, 5 Mar 2014 17:08:23 +0100 Subject: [PATCH 028/112] Add setRefreshToken method --- .../Model/Manipulator/ApiOauthRefreshTokenManipulator.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/Alchemy/Phrasea/Model/Manipulator/ApiOauthRefreshTokenManipulator.php b/lib/Alchemy/Phrasea/Model/Manipulator/ApiOauthRefreshTokenManipulator.php index c7e469bcd2..824e063bb5 100644 --- a/lib/Alchemy/Phrasea/Model/Manipulator/ApiOauthRefreshTokenManipulator.php +++ b/lib/Alchemy/Phrasea/Model/Manipulator/ApiOauthRefreshTokenManipulator.php @@ -59,6 +59,12 @@ class ApiOauthRefreshTokenManipulator implements ManipulatorInterface $this->om->flush(); } + public function setRefreshToken(ApiOauthRefreshtoken $refreshToken, $token) + { + $refreshToken->setRefreshToken($token); + $this->update($refreshToken); + } + private function getNewToken() { do { From b75d331a053b4a7f26255b7ef68fc25b3485acd7 Mon Sep 17 00:00:00 2001 From: Nicolas Le Goff Date: Wed, 5 Mar 2014 17:17:37 +0100 Subject: [PATCH 029/112] Add findByUserAndApplication method --- .../Model/Repositories/ApiAccountRepository.php | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/lib/Alchemy/Phrasea/Model/Repositories/ApiAccountRepository.php b/lib/Alchemy/Phrasea/Model/Repositories/ApiAccountRepository.php index 907ceb957c..18f414abfe 100644 --- a/lib/Alchemy/Phrasea/Model/Repositories/ApiAccountRepository.php +++ b/lib/Alchemy/Phrasea/Model/Repositories/ApiAccountRepository.php @@ -2,6 +2,7 @@ namespace Alchemy\Phrasea\Model\Repositories; +use Alchemy\Phrasea\Model\Entities\ApiApplication; use Doctrine\ORM\EntityRepository; /** @@ -12,4 +13,14 @@ use Doctrine\ORM\EntityRepository; */ class ApiAccountRepository extends EntityRepository { + public function findByUserAndApplication(User $user, ApiApplication $application) + { + $qb = $this->createQueryBuilder('acc'); + $qb->where($qb->expr()->eq('acc.user', ':user')); + $qb->andWhere($qb->expr()->eq('acc.application', ':app')); + $qb->setParameter(':user', $user); + $qb->setParameter(':app', $application); + + return $qb->getQuery()->getOneOrNullResult(); + } } From bc92e8049d3ab124e61f87a904a224385561d509 Mon Sep 17 00:00:00 2001 From: Nicolas Le Goff Date: Wed, 5 Mar 2014 17:43:59 +0100 Subject: [PATCH 030/112] Refactor lib/classes/API/OAuth2/Adapter.php to use api entities --- lib/classes/API/OAuth2/Adapter.php | 522 +++++++++++++++-------------- 1 file changed, 274 insertions(+), 248 deletions(-) diff --git a/lib/classes/API/OAuth2/Adapter.php b/lib/classes/API/OAuth2/Adapter.php index b3a2d9ee70..188c0ec0e9 100644 --- a/lib/classes/API/OAuth2/Adapter.php +++ b/lib/classes/API/OAuth2/Adapter.php @@ -13,6 +13,9 @@ use Alchemy\Phrasea\Application; use Alchemy\Phrasea\Authentication\Exception\AccountLockedException; use Alchemy\Phrasea\Authentication\Exception\RequireCaptchaException; +use Alchemy\Phrasea\Exception\RuntimeException; +use Alchemy\Phrasea\Model\Entities\ApiApplication; +use Alchemy\Phrasea\Model\Entities\Session; use Symfony\Component\HttpFoundation\Request; class API_OAuth2_Adapter extends OAuth2 @@ -24,7 +27,7 @@ class API_OAuth2_Adapter extends OAuth2 /** * - * @var API_OAuth2_Application + * @var ApiApplication */ protected $client; @@ -84,10 +87,8 @@ class API_OAuth2_Adapter extends OAuth2 public function __construct(Application $app) { parent::__construct(); - $this->params = []; $this->app = $app; - - return $this; + $this->params = []; } /** @@ -101,7 +102,7 @@ class API_OAuth2_Adapter extends OAuth2 /** * - * @return API_OAuth2_Application + * @return ApiApplication */ public function getClient() { @@ -125,11 +126,7 @@ class API_OAuth2_Adapter extends OAuth2 return $this->token; } - /** - * - * @param API_OAuth2_Application $client - */ - public function setClient(API_OAuth2_Application $client) + public function setClient(ApiApplication $client) { $this->client = $client; @@ -164,91 +161,85 @@ class API_OAuth2_Adapter extends OAuth2 } /** - * * Implements OAuth2::checkClientCredentials(). * - * @param string $client_id - * @param string $client_secret + * @param string $clientId + * @param string $clientSecret * @return boolean */ - protected function checkClientCredentials($client_id, $client_secret = NULL) + protected function checkClientCredentials($clientId, $clientSecret = null) { - try { - $application = API_OAuth2_Application::load_from_client_id($this->app, $client_id); - - if ($client_secret === NULL) { - return true; - } - - return ($application->get_client_secret() === $client_secret); - } catch (\Exception $e) { - + if (null === $application = $this->app['repo.api-applications']->findByClientId($clientId)) { + return false; } - return false; + if (null === $clientSecret) { + return true; + } + + return $application->getClientSecret() === $clientSecret; } /** * * Implements OAuth2::getRedirectUri(). * - * @param string $client_id - * @return string + * @param $clientId + * + * @return mixed + * @throws RuntimeException */ - protected function getRedirectUri($client_id) + protected function getRedirectUri($clientId) { - $application = API_OAuth2_Application::load_from_client_id($this->app, $client_id); + if (null === $application = $this->app['repo.api-applications']->findByClientId($clientId)) { + throw new RuntimeException(sprintf('Application with client id %s could not be found', $clientId)); + } - return $application->get_redirect_uri(); + return $application->getRedirectUri(); } /** * * Implements OAuth2::getAccessToken(). * - * @param string $oauth_token + * @param string $oauthToken * @return array */ - protected function getAccessToken($oauth_token) + protected function getAccessToken($oauthToken) { - $result = null; - - try { - $token = API_OAuth2_Token::load_by_oauth_token($this->app, $oauth_token); - - $result = [ - 'scope' => $token->get_scope() - , 'expires' => $token->get_expires() - , 'client_id' => $token->get_account()->get_application()->get_client_id() - , 'session_id' => $token->get_session_id() - , 'revoked' => ($token->get_account()->is_revoked() ? '1' : '0') - , 'usr_id' => $token->get_account()->get_user()->getId() - , 'oauth_token' => $token->get_value() - ]; - - } catch (\Exception $e) { - + if (null === $token = $this->app['repo.api-oauth-tokens']->find($oauthToken)) { + return null; } - return $result; + return [ + 'scope' => $token->getScope(), + 'expires' => $token->getExpire()->getTimestamp(), + 'client_id' => $token->getAccount()->getApplication()->getClientId(), + 'session_id' => null === $token->getSession() ? null : $token->getSession()->getId(), + 'revoked' => (int) $token->getAccount()->isRevoked(), + 'usr_id' => $token->getAccount()->getUser()->getId(), + 'oauth_token' => $token->getOauthToken(), + ]; } /** * Implements OAuth2::setAccessToken(). - */ - - /** * - * @param type $oauth_token - * @param type $account_id - * @param type $expires - * @param string $scope - * @return API_OAuth2_Adapter + * @param $oauthToken + * @param $accountId + * @param $expires + * @param null $scope + * + * @return $this + * @throws RuntimeException */ - protected function setAccessToken($oauth_token, $account_id, $expires, $scope = NULL) + protected function setAccessToken($oauthToken, $accountId, $expires, $scope = null) { - $account = new API_OAuth2_Account($this->app, $account_id); - $token = API_OAuth2_Token::create($this->app['phraseanet.appbox'], $account, $this->app['random.medium'], $scope); - $token->set_value($oauth_token)->set_expires($expires); + if (null === $account = $this->app['repo.api-accounts']->find($accountId)) { + throw new RuntimeException(sprintf('Account with id %s is not valid', $accountId)); + } + + $token = $this->app['manipulator.api-oauth-token']->create($account, null, \DateTime::createFromFormat('U', $expires), $scope); + $this->app['manipulator.api-oauth-token']->setOauthToken($token, $oauthToken); return $this; } @@ -279,87 +270,106 @@ class API_OAuth2_Adapter extends OAuth2 } /** - * * Overrides OAuth2::getAuthCode(). * - * @return array + * @param $code + * + * @return array|null */ protected function getAuthCode($code) { - try { - $code = new API_OAuth2_AuthCode($this->app, $code); - - return [ - 'redirect_uri' => $code->get_redirect_uri() - , 'client_id' => $code->get_account()->get_application()->get_client_id() - , 'expires' => $code->get_expires() - , 'account_id' => $code->get_account()->get_id() - ]; - } catch (\Exception $e) { - + if (null === $code = $this->app['repo.api-oauth-codes']->find($code)) { + return null; } - return null; + return [ + 'redirect_uri' => $code->getRedirectUri(), + 'client_id' => $code->getAccount()->getApplication()->getClientId(), + 'expires' => $code->getExpires()->getTimestamp(), + 'account_id' => $code->getAccount()->getId(), + ]; } /** * * Overrides OAuth2::setAuthCode(). + * + * @param $oauthCode + * @param $accountId + * @param $redirectUri + * @param $expires + * @param null $scope * - * @param string $code - * @param int $account_id - * @param string $redirect_uri - * @param string $expires - * @param string $scope - * @return API_OAuth2_Adapter + * @return $this|void + * @throws RuntimeException */ - protected function setAuthCode($code, $account_id, $redirect_uri, $expires, $scope = NULL) + protected function setAuthCode($oauthCode, $accountId, $redirectUri, $expires, $scope = null) { - $account = new API_OAuth2_Account($this->app, $account_id); - $code = API_OAuth2_AuthCode::create($this->app, $account, $code, $expires); - $code->set_redirect_uri($redirect_uri)->set_scope($scope); + if (null === $account = $this->app['repo.api-accounts']->find($accountId)) { + throw new RuntimeException(sprintf('Account with id %s is not valid', $accountId)); + } + + $code = $this->app['manipulator.api-oauth-code']->create($account, \DateTime::createFromFormat('U', $expires), $scope); + $this->app['manipulator.api-oauth-code']->setCode($code, $oauthCode); return $this; } /** * Overrides OAuth2::setRefreshToken(). + * + * @param $refreshToken + * @param $accountId + * @param $expires + * @param null $scope + * + * @return $this|void + * @throws RuntimeException */ - protected function setRefreshToken($refresh_token, $account_id, $expires, $scope = NULL) + protected function setRefreshToken($refreshToken, $accountId, $expires, $scope = null) { - $account = new API_OAuth2_Account($this->app, $account_id); - API_OAuth2_RefreshToken::create($this->app, $account, $expires, $refresh_token, $scope); + if (null === $account = $this->app['repo.api-accounts']->find($accountId)) { + throw new RuntimeException(sprintf('Account with id %s is not valid', $accountId)); + } + + $token = $this->app['manipulator.api-oauth-refresh-token']->create($account, \DateTime::createFromFormat('U', $expires), $scope); + $this->app['manipulator.api-oauth-refresh-token']->setRefreshToken($token, $refreshToken); return $this; } /** * Overrides OAuth2::getRefreshToken(). + * + * @param $refreshToken + * + * @return array|null */ - protected function getRefreshToken($refresh_token) + protected function getRefreshToken($refreshToken) { - try { - $token = new API_OAuth2_RefreshToken($this->app, $refresh_token); - - return [ - 'token' => $token->get_value() - , 'expires' => $token->get_expires()->format('U') - , 'client_id' => $token->get_account()->get_application()->get_client_id() - ]; - } catch (\Exception $e) { - + if (null === $token = $this->app['repo.api-oauth-refresh-token']->find($refreshToken)) { + return null; } - return null; + return [ + 'token' => $token->getRefreshToken(), + 'expires' => $token->getExpires()->getTimestamp(), + 'client_id' => $token->getAccount()->getApplication()->getClientId() + ]; } /** * Overrides OAuth2::unsetRefreshToken(). + * + * @param $refreshToken + * + * @return $this|void */ - protected function unsetRefreshToken($refresh_token) + protected function unsetRefreshToken($refreshToken) { - $token = new API_OAuth2_RefreshToken($this->app, $refresh_token); - $token->delete(); + if (null !== $token = $this->app['repo.api-oauth-refresh-token']->find($refreshToken)) { + $this->app['manipulator.api-oauth-refresh-token']->delete($token); + } return $this; } @@ -371,22 +381,21 @@ class API_OAuth2_Adapter extends OAuth2 */ public function getAuthorizationRequestParameters(Request $request) { - - $datas = [ - 'response_type' => $request->get('response_type', false) - , 'client_id' => $request->get('client_id', false) - , 'redirect_uri' => $request->get('redirect_uri', false) + $data = [ + 'response_type' => $request->get('response_type', false), + 'client_id' => $request->get('client_id', false), + 'redirect_uri' => $request->get('redirect_uri', false), ]; $scope = $request->get('scope', false); $state = $request->get('state', false); if ($state) { - $datas["state"] = $state; + $data["state"] = $state; } if ($scope) { - $datas["scope"] = $scope; + $data["scope"] = $scope; } $filters = [ @@ -405,17 +414,16 @@ class API_OAuth2_Adapter extends OAuth2 , "scope" => ["flags" => FILTER_REQUIRE_SCALAR] ]; - $input = filter_var_array($datas, $filters); + $input = filter_var_array($data, $filters); /** * check for valid client_id * check for valid redirect_uri */ if (! $input["client_id"]) { - if ($input["redirect_uri"]) - $this->errorDoRedirectUriCallback( - $input["redirect_uri"], OAUTH2_ERROR_INVALID_CLIENT, NULL, NULL, $input["state"] - ); + if ($input["redirect_uri"]) { + $this->errorDoRedirectUriCallback($input["redirect_uri"], OAUTH2_ERROR_INVALID_CLIENT, null, null, $input["state"]); + } // We don't have a good URI to use $this->errorJsonResponse(OAUTH2_HTTP_FOUND, OAUTH2_ERROR_INVALID_CLIENT); } @@ -424,68 +432,71 @@ class API_OAuth2_Adapter extends OAuth2 * redirect_uri is not required if already established via other channels * check an existing redirect URI against the one supplied */ - $redirect_uri = $this->getRedirectUri($input["client_id"]); + $redirectUri = $this->getRedirectUri($input["client_id"]); /** * At least one of: existing redirect URI or input redirect URI must be specified */ - if ( ! $redirect_uri && ! $input["redirect_uri"]) - $this->errorJsonResponse( - OAUTH2_HTTP_FOUND, OAUTH2_ERROR_INVALID_REQUEST); - + if ( ! $redirectUri && ! $input["redirect_uri"]) { + $this->errorJsonResponse(OAUTH2_HTTP_FOUND, OAUTH2_ERROR_INVALID_REQUEST); + } /** - * getRedirectUri() should return FALSE if the given client ID is invalid + * getRedirectUri() should return false if the given client ID is invalid * this probably saves us from making a separate db call, and simplifies the method set */ - if ($redirect_uri === FALSE) - $this->errorDoRedirectUriCallback( - $input["redirect_uri"], OAUTH2_ERROR_INVALID_CLIENT, NULL, NULL, $input["state"]); + if ($redirectUri === false) { + $this->errorDoRedirectUriCallback($input["redirect_uri"], OAUTH2_ERROR_INVALID_CLIENT, null, null, $input["state"]); + } /** * If there's an existing uri and one from input, verify that they match */ - if ($redirect_uri && $input["redirect_uri"]) { + if ($redirectUri && $input["redirect_uri"]) { /** * Ensure that the input uri starts with the stored uri */ $compare = strcasecmp( substr( - $input["redirect_uri"], 0, strlen($redirect_uri) - ), $redirect_uri); - if ($compare !== 0) - $this->errorDoRedirectUriCallback($input["redirect_uri"], OAUTH2_ERROR_REDIRECT_URI_MISMATCH, NULL, NULL, $input["state"]); - } elseif ($redirect_uri) { + $input["redirect_uri"], 0, strlen($redirectUri) + ), $redirectUri); + if ($compare !== 0) { + $this->errorDoRedirectUriCallback($input["redirect_uri"], OAUTH2_ERROR_REDIRECT_URI_MISMATCH, null, null, $input["state"]); + } + } elseif ($redirectUri) { /** * They did not provide a uri from input, so use the stored one */ - $input["redirect_uri"] = $redirect_uri; + $input["redirect_uri"] = $redirectUri; } /** * Check response_type */ if (! $input["response_type"]) { - $this->errorDoRedirectUriCallback($input["redirect_uri"], OAUTH2_ERROR_INVALID_REQUEST, 'Invalid response type.', NULL, $input["state"]); + $this->errorDoRedirectUriCallback($input["redirect_uri"], OAUTH2_ERROR_INVALID_REQUEST, 'Invalid response type.', null, $input["state"]); } /** * Check requested auth response type against the list of supported types */ - if (array_search($input["response_type"], $this->getSupportedAuthResponseTypes()) === FALSE) - $this->errorDoRedirectUriCallback($input["redirect_uri"], OAUTH2_ERROR_UNSUPPORTED_RESPONSE_TYPE, NULL, NULL, $input["state"]); + if (array_search($input["response_type"], $this->getSupportedAuthResponseTypes()) === false) { + $this->errorDoRedirectUriCallback($input["redirect_uri"], OAUTH2_ERROR_UNSUPPORTED_RESPONSE_TYPE, null, null, $input["state"]); + } /** * Restrict clients to certain authorization response types */ - if ($this->checkRestrictedAuthResponseType($input["client_id"], $input["response_type"]) === FALSE) - $this->errorDoRedirectUriCallback($input["redirect_uri"], OAUTH2_ERROR_UNAUTHORIZED_CLIENT, NULL, NULL, $input["state"]); + if ($this->checkRestrictedAuthResponseType($input["client_id"], $input["response_type"]) === false) { + $this->errorDoRedirectUriCallback($input["redirect_uri"], OAUTH2_ERROR_UNAUTHORIZED_CLIENT, null, null, $input["state"]); + } /** * Validate that the requested scope is supported */ - if ($input["scope"] && ! $this->checkScope($input["scope"], $this->getSupportedScopes())) - $this->errorDoRedirectUriCallback($input["redirect_uri"], OAUTH2_ERROR_INVALID_SCOPE, NULL, NULL, $input["state"]); + if ($input["scope"] && ! $this->checkScope($input["scope"], $this->getSupportedScopes())) { + $this->errorDoRedirectUriCallback($input["redirect_uri"], OAUTH2_ERROR_INVALID_SCOPE, null, null, $input["state"]); + } /** * at this point all params are ok @@ -496,121 +507,101 @@ class API_OAuth2_Adapter extends OAuth2 } /** + * @param User $user * - * @param User $user - * @return API_OAuth2_Account + * @return mixed + * @throws logicalException */ public function updateAccount(User $user) { - if ($this->client === null) + if ($this->client === null) { throw new logicalException("Client property must be set before update an account"); + } - try { - $account = API_OAuth2_Account::load_with_user($this->app, $this->client, $user); - } catch (\Exception $e) { - $account = $this->createAccount($user->getId()); + if (null === $account = $this->app['repo.api-accounts']->findByUserAndApplication($user, $this->client)) { + $account = $this->app['manipulator.api-account']->create($this->client, $user); } return $account; } /** + * @param $is_authorized + * @param array $params * - * @param int $usr_id - * @return API_OAuth2_Account - */ - private function createAccount($usr_id) - { - $user = $this->app['repo.users']->find($usr_id); - - return API_OAuth2_Account::create($this->app, $user, $this->client); - } - - /** - * - * @param $is_authorized - * @param array $params - * @return string + * @return array */ public function finishNativeClientAuthorization($is_authorized, $params = []) { $result = []; - $params += [ - 'scope' => NULL, - 'state' => NULL, - ]; - extract($params); + $params += ['scope' => null, 'state' => null,]; - if ($state !== NULL) - $result["query"]["state"] = $state; + if ($params['state'] !== null) { + $result["query"]["state"] = $params['state'] ; + } - if ($is_authorized === FALSE) { + if ($is_authorized === false) { $result["error"] = OAUTH2_ERROR_USER_DENIED; } else { - if ($response_type == OAUTH2_AUTH_RESPONSE_TYPE_AUTH_CODE) - $result["code"] = $this->createAuthCode($account_id, $redirect_uri, $scope); + if ($params['response_type'] === OAUTH2_AUTH_RESPONSE_TYPE_AUTH_CODE) { + $result["code"] = $this->createAuthCode($params['account_id'], $params['redirect_uri'], $params['scope']); + } - if ($response_type == OAUTH2_AUTH_RESPONSE_TYPE_ACCESS_TOKEN) + if ($params['response_type'] === OAUTH2_AUTH_RESPONSE_TYPE_ACCESS_TOKEN) { $result["error"] = OAUTH2_ERROR_UNSUPPORTED_RESPONSE_TYPE; + } } return $result; } /** + * @param $redirectUri * - * @param $redirect_uri - * @return + * @return bool */ - public function isNativeApp($redirect_uri) + public function isNativeApp($redirectUri) { - return $redirect_uri === API_OAuth2_Application::NATIVE_APP_REDIRECT_URI; + return $redirectUri === ApiApplication::NATIVE_APP_REDIRECT_URI; } - public function remember_this_ses_id($ses_id) + public function rememberSession(Session $session) { - try { - $token = API_OAuth2_Token::load_by_oauth_token($this->app, $this->token); - $token->set_session_id($ses_id); - - return true; - } catch (\Exception $e) { - + if (null !== $token = $this->app['repo.api-oauth-tokens']->find($this->token)) { + $token->setSession($session); } - - return false; } - public function verifyAccessToken($scope = NULL, $exit_not_present = TRUE, $exit_invalid = TRUE, $exit_expired = TRUE, $exit_scope = TRUE, $realm = NULL) + public function verifyAccessToken($scope = null, $exit_not_present = TRUE, $exit_invalid = TRUE, $exit_expired = TRUE, $exit_scope = TRUE, $realm = null) { $token_param = $this->getAccessTokenParams(); // Access token was not provided if ($token_param === false) { - return $exit_not_present ? $this->errorWWWAuthenticateResponseHeader(OAUTH2_HTTP_BAD_REQUEST, $realm, OAUTH2_ERROR_INVALID_REQUEST, 'The request is missing a required parameter, includes an unsupported parameter or parameter value, repeats the same parameter, uses more than one method for including an access token, or is otherwise malformed.', NULL, $scope) : FALSE; + return $exit_not_present ? $this->errorWWWAuthenticateResponseHeader(OAUTH2_HTTP_BAD_REQUEST, $realm, OAUTH2_ERROR_INVALID_REQUEST, 'The request is missing a required parameter, includes an unsupported parameter or parameter value, repeats the same parameter, uses more than one method for including an access token, or is otherwise malformed.', null, $scope) : false; } // Get the stored token data (from the implementing subclass) $token = $this->getAccessToken($token_param); - if ($token === NULL) { - return $exit_invalid ? $this->errorWWWAuthenticateResponseHeader(OAUTH2_HTTP_UNAUTHORIZED, $realm, OAUTH2_ERROR_INVALID_TOKEN, 'The access token provided is invalid.', NULL, $scope) : FALSE; + if ($token === null) { + return $exit_invalid ? $this->errorWWWAuthenticateResponseHeader(OAUTH2_HTTP_UNAUTHORIZED, $realm, OAUTH2_ERROR_INVALID_TOKEN, 'The access token provided is invalid.', null, $scope) : false; } if (isset($token['revoked']) && $token['revoked']) { - return $exit_invalid ? $this->errorWWWAuthenticateResponseHeader(OAUTH2_HTTP_UNAUTHORIZED, $realm, OAUTH2_ERROR_INVALID_TOKEN, 'End user has revoked access to his personal datas for your application.', NULL, $scope) : FALSE; + return $exit_invalid ? $this->errorWWWAuthenticateResponseHeader(OAUTH2_HTTP_UNAUTHORIZED, $realm, OAUTH2_ERROR_INVALID_TOKEN, 'End user has revoked access to his personal datas for your application.', null, $scope) : false; } if ($this->enable_expire) { // Check token expiration (I'm leaving this check separated, later we'll fill in better error messages) if (isset($token["expires"]) && time() > $token["expires"]) { - return $exit_expired ? $this->errorWWWAuthenticateResponseHeader(OAUTH2_HTTP_UNAUTHORIZED, $realm, OAUTH2_ERROR_EXPIRED_TOKEN, 'The access token provided has expired.', NULL, $scope) : FALSE; + return $exit_expired ? $this->errorWWWAuthenticateResponseHeader(OAUTH2_HTTP_UNAUTHORIZED, $realm, OAUTH2_ERROR_EXPIRED_TOKEN, 'The access token provided has expired.', null, $scope) : false; } } // Check scope, if provided - // If token doesn't have a scope, it's NULL/empty, or it's insufficient, then throw an error + // If token doesn't have a scope, it's null/empty, or it's insufficient, then throw an error if ($scope && ( ! isset($token["scope"]) || ! $token["scope"] || ! $this->checkScope($scope, $token["scope"]))) { - return $exit_scope ? $this->errorWWWAuthenticateResponseHeader(OAUTH2_HTTP_FORBIDDEN, $realm, OAUTH2_ERROR_INSUFFICIENT_SCOPE, 'The request requires higher privileges than provided by the access token.', NULL, $scope) : FALSE; + return $exit_scope ? $this->errorWWWAuthenticateResponseHeader(OAUTH2_HTTP_FORBIDDEN, $realm, OAUTH2_ERROR_INSUFFICIENT_SCOPE, 'The request requires higher privileges than provided by the access token.', null, $scope) : false; } //save token's linked ses_id $this->session_id = $token['session_id']; @@ -623,23 +614,25 @@ class API_OAuth2_Adapter extends OAuth2 public function finishClientAuthorization($is_authorized, $params = []) { $params += [ - 'scope' => NULL, - 'state' => NULL, + 'scope' => null, + 'state' => null, ]; - extract($params); - if ($state !== NULL) - $result["query"]["state"] = $state; - if ($is_authorized === FALSE) - $result["query"]["error"] = OAUTH2_ERROR_USER_DENIED; - else { - if ($response_type == OAUTH2_AUTH_RESPONSE_TYPE_AUTH_CODE || $response_type == OAUTH2_AUTH_RESPONSE_TYPE_CODE_AND_TOKEN) - $result["query"]["code"] = $this->createAuthCode($account_id, $redirect_uri, $scope); - - if ($response_type == OAUTH2_AUTH_RESPONSE_TYPE_ACCESS_TOKEN || $response_type == OAUTH2_AUTH_RESPONSE_TYPE_CODE_AND_TOKEN) - $result["fragment"] = $this->createAccessToken($account_id, $scope); + if ($params['state'] !== null) { + $result["query"]["state"] = $params['state']; } - $this->doRedirectUriCallback($redirect_uri, $result); + if ($is_authorized === false) { + $result["query"]["error"] = OAUTH2_ERROR_USER_DENIED; + } else { + if ($params['response_type'] == OAUTH2_AUTH_RESPONSE_TYPE_AUTH_CODE || $params['response_type'] == OAUTH2_AUTH_RESPONSE_TYPE_CODE_AND_TOKEN) { + $result["query"]["code"] = $this->createAuthCode($params['account_id'], $params['redirect_uri'], $params['scope']); + } + + if ($params['response_type'] == OAUTH2_AUTH_RESPONSE_TYPE_ACCESS_TOKEN || $params['response_type'] == OAUTH2_AUTH_RESPONSE_TYPE_CODE_AND_TOKEN) { + $result["fragment"] = $this->createAccessToken($params['account_id'], $params['scope']); + } + } + $this->doRedirectUriCallback($params['redirect_uri'], $result); } /** @@ -648,62 +641,75 @@ class API_OAuth2_Adapter extends OAuth2 public function grantAccessToken() { $filters = [ - "grant_type" => ["filter" => FILTER_VALIDATE_REGEXP, "options" => ["regexp" => OAUTH2_GRANT_TYPE_REGEXP], "flags" => FILTER_REQUIRE_SCALAR], - "scope" => ["flags" => FILTER_REQUIRE_SCALAR], - "code" => ["flags" => FILTER_REQUIRE_SCALAR], - "redirect_uri" => ["filter" => FILTER_SANITIZE_URL], - "username" => ["flags" => FILTER_REQUIRE_SCALAR], - "password" => ["flags" => FILTER_REQUIRE_SCALAR], - "assertion_type" => ["flags" => FILTER_REQUIRE_SCALAR], - "assertion" => ["flags" => FILTER_REQUIRE_SCALAR], + "grant_type" => [ + "filter" => FILTER_VALIDATE_REGEXP, + "options" => ["regexp" => OAUTH2_GRANT_TYPE_REGEXP], + "flags" => FILTER_REQUIRE_SCALAR + ], + "scope" => ["flags" => FILTER_REQUIRE_SCALAR], + "code" => ["flags" => FILTER_REQUIRE_SCALAR], + "redirect_uri" => ["filter" => FILTER_SANITIZE_URL], + "username" => ["flags" => FILTER_REQUIRE_SCALAR], + "password" => ["flags" => FILTER_REQUIRE_SCALAR], + "assertion_type" => ["flags" => FILTER_REQUIRE_SCALAR], + "assertion" => ["flags" => FILTER_REQUIRE_SCALAR], "refresh_token" => ["flags" => FILTER_REQUIRE_SCALAR], ]; $input = filter_input_array(INPUT_POST, $filters); // Grant Type must be specified. - if ( ! $input["grant_type"]) + if ( ! $input["grant_type"]) { $this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_INVALID_REQUEST, 'Invalid grant_type parameter or parameter missing'); + } // Make sure we've implemented the requested grant type - if ( ! in_array($input["grant_type"], $this->getSupportedGrantTypes())) + if ( ! in_array($input["grant_type"], $this->getSupportedGrantTypes())) { $this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_UNSUPPORTED_GRANT_TYPE); + } // Authorize the client $client = $this->getClientCredentials(); - if ($this->checkClientCredentials($client[0], $client[1]) === FALSE) + if ($this->checkClientCredentials($client[0], $client[1]) === false) { $this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_INVALID_CLIENT); + } - if ( ! $this->checkRestrictedGrantType($client[0], $input["grant_type"])) + if ( ! $this->checkRestrictedGrantType($client[0], $input["grant_type"])) { $this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_UNAUTHORIZED_CLIENT); + } - if ( ! $this->checkRestrictedGrantType($client[0], $input["grant_type"])) + if ( ! $this->checkRestrictedGrantType($client[0], $input["grant_type"])) { $this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_UNAUTHORIZED_CLIENT); + } // Do the granting switch ($input["grant_type"]) { case OAUTH2_GRANT_TYPE_AUTH_CODE: - if ( ! $input["code"] || ! $input["redirect_uri"]) + if ( ! $input["code"] || ! $input["redirect_uri"]) { $this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_INVALID_REQUEST); + } $stored = $this->getAuthCode($input["code"]); // Ensure that the input uri starts with the stored uri - if ($stored === NULL || (strcasecmp(substr($input["redirect_uri"], 0, strlen($stored["redirect_uri"])), $stored["redirect_uri"]) !== 0) || $client[0] != $stored["client_id"]) + if ($stored === null || (strcasecmp(substr($input["redirect_uri"], 0, strlen($stored["redirect_uri"])), $stored["redirect_uri"]) !== 0) || $client[0] != $stored["client_id"]) { $this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_INVALID_GRANT); + } - if ($stored["expires"] < time()) + if ($stored["expires"] < time()) { $this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_EXPIRED_TOKEN); + } break; case OAUTH2_GRANT_TYPE_USER_CREDENTIALS: - $application = API_OAuth2_Application::load_from_client_id($this->app, $client[0]); + $application = ApiApplication::load_from_client_id($this->app, $client[0]); if ( ! $application->is_password_granted()) { $this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_UNSUPPORTED_GRANT_TYPE, 'Password grant type is not enable for your client'); } - if ( ! $input["username"] || ! $input["password"]) + if ( ! $input["username"] || ! $input["password"]) { $this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_INVALID_REQUEST, 'Missing parameters. "username" and "password" required'); + } $stored = $this->checkUserCredentials($client[0], $input["username"], $input["password"]); @@ -712,26 +718,31 @@ class API_OAuth2_Adapter extends OAuth2 } break; case OAUTH2_GRANT_TYPE_ASSERTION: - if ( ! $input["assertion_type"] || ! $input["assertion"]) + if ( ! $input["assertion_type"] || ! $input["assertion"]) { $this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_INVALID_REQUEST); + } $stored = $this->checkAssertion($client[0], $input["assertion_type"], $input["assertion"]); - if ($stored === FALSE) + if ($stored === false) { $this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_INVALID_GRANT); + } break; case OAUTH2_GRANT_TYPE_REFRESH_TOKEN: - if ( ! $input["refresh_token"]) + if ( ! $input["refresh_token"]) { $this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_INVALID_REQUEST, 'No "refresh_token" parameter found'); + } $stored = $this->getRefreshToken($input["refresh_token"]); - if ($stored === NULL || $client[0] != $stored["client_id"]) + if ($stored === null || $client[0] != $stored["client_id"]) { $this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_INVALID_GRANT); + } - if ($stored["expires"] < time()) + if ($stored["expires"] < time()) { $this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_EXPIRED_TOKEN); + } // store the refresh token locally so we can delete it when a new refresh token is generated $this->setVariable('_old_refresh_token', $stored["token"]); @@ -740,16 +751,19 @@ class API_OAuth2_Adapter extends OAuth2 case OAUTH2_GRANT_TYPE_NONE: $stored = $this->checkNoneAccess($client[0]); - if ($stored === FALSE) + if ($stored === false) { $this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_INVALID_REQUEST); + } } // Check scope, if provided - if ($input["scope"] && ( ! is_array($stored) || ! isset($stored["scope"]) || ! $this->checkScope($input["scope"], $stored["scope"]))) + if ($input["scope"] && ( ! is_array($stored) || ! isset($stored["scope"]) || ! $this->checkScope($input["scope"], $stored["scope"]))) { $this->errorJsonResponse(OAUTH2_HTTP_BAD_REQUEST, OAUTH2_ERROR_INVALID_SCOPE); + } - if ( ! $input["scope"]) - $input["scope"] = NULL; + if ( ! $input["scope"]) { + $input["scope"] = null; + } $token = $this->createAccessToken($stored['account_id'], $input["scope"]); $this->sendJsonHeaders(); @@ -759,51 +773,63 @@ class API_OAuth2_Adapter extends OAuth2 return; } - protected function createAccessToken($account_id, $scope = NULL) + protected function createAccessToken($accountId, $scope = null) { $token = [ "access_token" => $this->genAccessToken(), "scope" => $scope ]; - if ($this->enable_expire) + if ($this->enable_expire) { $token['expires_in'] = $this->getVariable('access_token_lifetime', OAUTH2_DEFAULT_ACCESS_TOKEN_LIFETIME); + } - $this->setAccessToken($token["access_token"], $account_id, time() + $this->getVariable('access_token_lifetime', OAUTH2_DEFAULT_ACCESS_TOKEN_LIFETIME), $scope); + $this->setAccessToken($token["access_token"], $accountId, time() + $this->getVariable('access_token_lifetime', OAUTH2_DEFAULT_ACCESS_TOKEN_LIFETIME), $scope); // Issue a refresh token also, if we support them if (in_array(OAUTH2_GRANT_TYPE_REFRESH_TOKEN, $this->getSupportedGrantTypes())) { $token["refresh_token"] = $this->genAccessToken(); - $this->setRefreshToken($token["refresh_token"], $account_id, time() + $this->getVariable('refresh_token_lifetime', OAUTH2_DEFAULT_REFRESH_TOKEN_LIFETIME), $scope); + $this->setRefreshToken($token["refresh_token"], $accountId, time() + $this->getVariable('refresh_token_lifetime', OAUTH2_DEFAULT_REFRESH_TOKEN_LIFETIME), $scope); // If we've granted a new refresh token, expire the old one - if ($this->getVariable('_old_refresh_token')) + if ($this->getVariable('_old_refresh_token')) { $this->unsetRefreshToken($this->getVariable('_old_refresh_token')); + } } return $token; } - protected function checkUserCredentials($client_id, $username, $password) + /** + * @param $clientId + * @param $username + * @param $password + * + * @return array|boolean + */ + protected function checkUserCredentials($clientId, $username, $password) { try { - $this->setClient(API_OAuth2_Application::load_from_client_id($this->app, $client_id)); + if (null === $client = $this->app['repo.api-applications']->findByClientId($clientId)) { + return false; + } + $this->setClient($client); - $usr_id = $this->app['auth.native']->getUsrId($username, $password, Request::createFromGlobals()); + $usrId = $this->app['auth.native']->getUsrId($username, $password, Request::createFromGlobals()); - if (!$usr_id) { + if (!$usrId) { return false; } - if (null === $user = $this->app['repo.users']->find($usr_id)) { + if (null === $user = $this->app['repo.users']->find($usrId)) { return false; } $account = $this->updateAccount($user); return [ - 'redirect_uri' => $this->client->get_redirect_uri() - , 'client_id' => $this->client->get_client_id() - , 'account_id' => $account->get_id() + 'redirect_uri' => $this->client->getRedirectUri(), + 'client_id' => $this->client->getClient(), + 'account_id' => $account->getId(), ]; } catch (AccountLockedException $e) { return false; From 05877e49b830e87de138a9d53cb35c31449625a3 Mon Sep 17 00:00:00 2001 From: Nicolas Le Goff Date: Wed, 5 Mar 2014 18:11:16 +0100 Subject: [PATCH 031/112] Add method property --- lib/Alchemy/Phrasea/Model/Entities/ApiLog.php | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/lib/Alchemy/Phrasea/Model/Entities/ApiLog.php b/lib/Alchemy/Phrasea/Model/Entities/ApiLog.php index 9788b86924..78971b79cd 100644 --- a/lib/Alchemy/Phrasea/Model/Entities/ApiLog.php +++ b/lib/Alchemy/Phrasea/Model/Entities/ApiLog.php @@ -33,6 +33,13 @@ class ApiLog */ private $route; + /** + * @var string + * + * @ORM\Column(type="string", length=16, nullable=true) + */ + private $method; + /** * @Gedmo\Timestampable(on="create") * @ORM\Column(type="datetime") @@ -322,4 +329,24 @@ class ApiLog { return $this->id; } + + /** + * @param string $method + * + * @return ApiLog + */ + public function setMethod($method) + { + $this->method = $method; + + return $this; + } + + /** + * @return string + */ + public function getMethod() + { + return $this->method; + } } From 09885d71b5f62d062a92d82c99d6b03ffebb4f78 Mon Sep 17 00:00:00 2001 From: Nicolas Le Goff Date: Wed, 5 Mar 2014 18:17:31 +0100 Subject: [PATCH 032/112] Add some constant --- lib/Alchemy/Phrasea/Model/Entities/ApiLog.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/Alchemy/Phrasea/Model/Entities/ApiLog.php b/lib/Alchemy/Phrasea/Model/Entities/ApiLog.php index 78971b79cd..646d07754d 100644 --- a/lib/Alchemy/Phrasea/Model/Entities/ApiLog.php +++ b/lib/Alchemy/Phrasea/Model/Entities/ApiLog.php @@ -11,6 +11,11 @@ use Gedmo\Mapping\Annotation as Gedmo; */ class ApiLog { + const DATABOXES_RESOURCE = 'databoxes'; + const RECORDS_RESOURCE = 'record'; + const BASKETS_RESOURCE = 'baskets'; + const FEEDS_RESOURCE = 'feeds'; + /** * @ORM\Column(type="integer") * @ORM\Id From 8439826db183e32a2e60572de9065df8f4ac4b95 Mon Sep 17 00:00:00 2001 From: Nicolas Le Goff Date: Wed, 5 Mar 2014 18:19:04 +0100 Subject: [PATCH 033/112] Add ApiLog Manipulator --- .../Model/Manipulator/ApiLogManipulator.php | 130 ++++++++++++++++++ 1 file changed, 130 insertions(+) create mode 100644 lib/Alchemy/Phrasea/Model/Manipulator/ApiLogManipulator.php diff --git a/lib/Alchemy/Phrasea/Model/Manipulator/ApiLogManipulator.php b/lib/Alchemy/Phrasea/Model/Manipulator/ApiLogManipulator.php new file mode 100644 index 0000000000..87b8c8037e --- /dev/null +++ b/lib/Alchemy/Phrasea/Model/Manipulator/ApiLogManipulator.php @@ -0,0 +1,130 @@ +om = $om; + $this->repository = $repo; + } + + public function create(ApiAccount $account, Request $request, Response $response) + { + $log = new ApiLog(); + $log->setAccount($account); + $this->doSetFromHttpContext($log, $request, $response); + + $this->update($log); + + return $log; + } + + public function delete(ApiLog $log) + { + $this->om->remove($log); + $this->om->flush(); + } + + public function update(ApiLog $log) + { + $this->om->persist($log); + $this->om->flush(); + } + + private function doSetFromHttpContext(ApiLog $log, Request $request, Response $response) + { + $log->setRoute($request->getPathInfo()); + $log->setMethod($request->getMethod()); + $log->setStatusCode($response->getStatusCode()); + $log->setFormat($response->headers->get('content-type')); + $this->setDetails($log, $request, $response); + } + + /** + * Parses the requested route to fetch + * - the resource (databox, basket, record etc ..) + * - general action (list, add, search) + * - the action (setstatus, setname etc..) + * - the aspect (collections, related, content etc..) + * + * @param ApiLog $log + * @param Request $request + * @param Response $response + */ + private function setDetails(ApiLog $log, Request $request, Response $response) + { + $resource = $general = $aspect = $action = null; + $chunks = explode('/', trim($request->getPathInfo(), '/')); + + if (false === $response->isOk() || sizeof($chunks) === 0) { + return; + } + $resource = $chunks[0]; + + if (count($chunks) == 2 && (int) $chunks[1] == 0) { + $general = $chunks[1]; + } else { + switch ($resource) { + case ApiLog::DATABOXES_RESOURCE : + if ((int) $chunks[1] > 0 && count($chunks) == 3) { + $aspect = $chunks[2]; + } + break; + case ApiLog::RECORDS_RESOURCE : + if ((int) $chunks[1] > 0 && count($chunks) == 4) { + if (!isset($chunks[3])) { + $aspect = "record"; + } elseif (preg_match("/^set/", $chunks[3])) { + $action = $chunks[3]; + } else { + $aspect = $chunks[3]; + } + } + break; + case ApiLog::BASKETS_RESOURCE : + if ((int) $chunks[1] > 0 && count($chunks) == 3) { + if (preg_match("/^set/", $chunks[2]) || preg_match("/^delete/", $chunks[2])) { + $action = $chunks[2]; + } else { + $aspect = $chunks[2]; + } + } + break; + case ApiLog::FEEDS_RESOURCE : + if ((int) $chunks[1] > 0 && count($chunks) == 3) { + $aspect = $chunks[2]; + } + break; + } + } + + $log->setResource($resource); + $log->setGeneral($general); + $log->setAspect($aspect); + $log->setAction($action); + } +} From a9fbeeffd5a6103f49dbda2c1ab13abf1e1d0080 Mon Sep 17 00:00:00 2001 From: Nicolas Le Goff Date: Wed, 5 Mar 2014 18:24:54 +0100 Subject: [PATCH 034/112] Refactor V1 controller to use api entities --- lib/Alchemy/Phrasea/Controller/Api/V1.php | 117 ++++------------------ 1 file changed, 17 insertions(+), 100 deletions(-) diff --git a/lib/Alchemy/Phrasea/Controller/Api/V1.php b/lib/Alchemy/Phrasea/Controller/Api/V1.php index 030963df9c..0a106494d0 100644 --- a/lib/Alchemy/Phrasea/Controller/Api/V1.php +++ b/lib/Alchemy/Phrasea/Controller/Api/V1.php @@ -62,8 +62,11 @@ class V1 implements ControllerProviderInterface }); $controllers->after(function (Request $request, Response $response) use ($app) { - $this->logQuery($app, $request, $response); - $this->logout($app); + $app['manipulator.api-log']->create($app['session']->get('token')->getAccount(), $request, $response); + $app['session']->set('token', null); + if (null !== $app['authentication']->getUser()) { + $app['authentication']->closeAccount(); + } }); $controllers->get('/monitor/scheduler/', 'controller.api.v1:get_scheduler') @@ -1968,116 +1971,37 @@ class V1 implements ControllerProviderInterface $oauth2_adapter = new \API_OAuth2_Adapter($app); $oauth2_adapter->verifyAccessToken(); - $token = \API_OAuth2_Token::load_by_oauth_token($app, $oauth2_adapter->getToken()); + if (null === $token = $app['repo.api-oauth-tokens']->find($oauth2_adapter->getToken())) { + throw new NotFoundHttpException('Provided token is not valid.'); + } $app['session']->set('token', $token); - $oAuth2App = $token->get_account()->get_application(); - /* @var $oAuth2App \API_OAuth2_Application */ + $oAuth2Account = $token->getAccount(); + $oAuth2App = $oAuth2Account->getApplication(); - if ($oAuth2App->get_client_id() == \API_OAuth2_Application_Navigator::CLIENT_ID + if ($oAuth2App->getClientId() == \API_OAuth2_Application_Navigator::CLIENT_ID && !$app['conf']->get(['registry', 'api-clients', 'navigator-enabled'])) { - return Result::createError($request, 403, 'The use of phraseanet Navigator is not allowed')->createResponse(); + return Result::createError($request, 403, 'The use of Phraseanet Navigator is not allowed')->createResponse(); } - if ($oAuth2App->get_client_id() == \API_OAuth2_Application_OfficePlugin::CLIENT_ID + if ($oAuth2App->getClientId() == \API_OAuth2_Application_OfficePlugin::CLIENT_ID && ! $app['conf']->get(['registry', 'api-clients', 'office-enabled'])) { return Result::createError($request, 403, 'The use of Office Plugin is not allowed.')->createResponse(); } - $user = $app['repo.users']->find($oauth2_adapter->get_usr_id()); - $app['authentication']->openAccount($user); - $oauth2_adapter->remember_this_ses_id($app['session']->get('session_id')); + $app['authentication']->openAccount($oAuth2Account->getUser()); + $oauth2_adapter->rememberSession($app['session']); $app['dispatcher']->dispatch(PhraseaEvents::API_OAUTH2_END, new ApiOAuth2EndEvent()); } public function ensureAdmin(Request $request, Application $app) { - $user = $app['session']->get('token')->get_account()->get_user(); - if (!$app['acl']->get($user)->is_admin()) { + $user = $app['session']->get('token')->getAccount()->getUser(); + if (!$user->isAdmin()) { return Result::createError($request, 401, 'You are not authorized')->createResponse(); } } - private function logQuery(Application $app, Request $request, Response $response) - { - $infos = $this->parseRoute($request->getPathInfo(), $response); - - Logger::create( - $app, - $app['session']->get('token')->get_account(), - $request->getMethod() . " " . $request->getPathInfo(), - $response->getStatusCode(), - $response->headers->get('content-type'), - $infos['ressource'], - $infos['general'], - $infos['aspect'], - $infos['action'] - ); - } - - /** - * Parses the requested route to fetch - * - the ressource (databox, basket, record etc ..) - * - general action (list, add, search) - * - the action (setstatus, setname etc..) - * - the aspect (collections, related, content etc..) - * - * @return array - */ - private function parseRoute($route, Response $response) - { - $ressource = $general = $aspect = $action = null; - $exploded_route = explode('/', trim($route, '/')); - - if ($response->isOk() && sizeof($exploded_route) > 0) { - $ressource = $exploded_route[0]; - - if (count($exploded_route) == 2 && (int) $exploded_route[1] == 0) { - $general = $exploded_route[1]; - } else { - switch ($ressource) { - case Logger::DATABOXES_RESOURCE : - if ((int) $exploded_route[1] > 0 && count($exploded_route) == 3) { - $aspect = $exploded_route[2]; - } - break; - case Logger::RECORDS_RESOURCE : - if ((int) $exploded_route[1] > 0 && count($exploded_route) == 4) { - if (!isset($exploded_route[3])) { - $aspect = "record"; - } elseif (preg_match("/^set/", $exploded_route[3])) { - $action = $exploded_route[3]; - } else { - $aspect = $exploded_route[3]; - } - } - break; - case Logger::BASKETS_RESOURCE : - if ((int) $exploded_route[1] > 0 && count($exploded_route) == 3) { - if (preg_match("/^set/", $exploded_route[2]) || preg_match("/^delete/", $exploded_route[2])) { - $action = $exploded_route[2]; - } else { - $aspect = $exploded_route[2]; - } - } - break; - case Logger::FEEDS_RESOURCE : - if ((int) $exploded_route[1] > 0 && count($exploded_route) == 3) { - $aspect = $exploded_route[2]; - } - break; - } - } - } - - return [ - 'ressource' => $ressource, - 'general' => $general, - 'aspect' => $aspect, - 'action' => $action - ]; - } - private function list_user(User $user) { switch ($user->getGender()) { @@ -2117,11 +2041,4 @@ class V1 implements ControllerProviderInterface 'locale' => $user->getLocale() ?: null, ]; } - - private function logout(Application $app) - { - if (null !== $app['authentication']->getUser()) { - $app['authentication']->closeAccount(); - } - } } From b50734fb502c77c26efcd75522644ce2871df803 Mon Sep 17 00:00:00 2001 From: Nicolas Le Goff Date: Wed, 5 Mar 2014 18:25:22 +0100 Subject: [PATCH 035/112] Remove dead code --- lib/classes/API/OAuth2/Adapter.php | 20 ++------------------ 1 file changed, 2 insertions(+), 18 deletions(-) diff --git a/lib/classes/API/OAuth2/Adapter.php b/lib/classes/API/OAuth2/Adapter.php index 188c0ec0e9..1689ab9d5a 100644 --- a/lib/classes/API/OAuth2/Adapter.php +++ b/lib/classes/API/OAuth2/Adapter.php @@ -67,12 +67,6 @@ class API_OAuth2_Adapter extends OAuth2 */ protected $session_id; - /** - * - * @var string - */ - protected $usr_id_requested; - /** * access token of current request * @var string @@ -151,15 +145,6 @@ class API_OAuth2_Adapter extends OAuth2 return $this->session_id; } - /** - * - * @return int - */ - public function get_usr_id() - { - return $this->usr_id; - } - /** * Implements OAuth2::checkClientCredentials(). * @@ -572,7 +557,7 @@ class API_OAuth2_Adapter extends OAuth2 } } - public function verifyAccessToken($scope = null, $exit_not_present = TRUE, $exit_invalid = TRUE, $exit_expired = TRUE, $exit_scope = TRUE, $realm = null) + public function verifyAccessToken($scope = null, $exit_not_present = true, $exit_invalid = true, $exit_expired = true, $exit_scope = true, $realm = null) { $token_param = $this->getAccessTokenParams(); @@ -605,10 +590,9 @@ class API_OAuth2_Adapter extends OAuth2 } //save token's linked ses_id $this->session_id = $token['session_id']; - $this->usr_id = $token['usr_id']; $this->token = $token['oauth_token']; - return TRUE; + return true; } public function finishClientAuthorization($is_authorized, $params = []) From 2ffcb3898582d715147a4850d491d7ebcd73d84a Mon Sep 17 00:00:00 2001 From: Nicolas Le Goff Date: Wed, 5 Mar 2014 18:30:03 +0100 Subject: [PATCH 036/112] Use TokenManipulator::LETTERS_AND_NUMBERS instead of old random::LETTERS_AND_NUMBERS --- .../Phrasea/Model/Manipulator/ApiApplicationManipulator.php | 5 +++-- .../Phrasea/Model/Manipulator/ApiOauthCodeManipulator.php | 3 ++- .../Model/Manipulator/ApiOauthRefreshTokenManipulator.php | 3 ++- .../Phrasea/Model/Manipulator/ApiOauthTokenManipulator.php | 3 ++- 4 files changed, 9 insertions(+), 5 deletions(-) diff --git a/lib/Alchemy/Phrasea/Model/Manipulator/ApiApplicationManipulator.php b/lib/Alchemy/Phrasea/Model/Manipulator/ApiApplicationManipulator.php index f7b0c4b3d8..b8226ef00d 100644 --- a/lib/Alchemy/Phrasea/Model/Manipulator/ApiApplicationManipulator.php +++ b/lib/Alchemy/Phrasea/Model/Manipulator/ApiApplicationManipulator.php @@ -16,6 +16,7 @@ use Alchemy\Phrasea\Authentication\ACLProvider; use Alchemy\Phrasea\Exception\InvalidArgumentException; use Alchemy\Phrasea\Model\Entities\ApiApplication; use Alchemy\Phrasea\Model\Entities\User; +use Alchemy\Phrasea\Model\Manipulator\TokenManipulator; use Doctrine\Common\Persistence\ObjectManager; use Doctrine\ORM\EntityRepository; use RandomLib\Generator; @@ -43,8 +44,8 @@ class ApiApplicationManipulator implements ManipulatorInterface $this->doSetWebsiteUrl($application, $applicationWebsite); $this->doSetRedirectUri($application, $redirectUri); $application->setNonce($this->randomGenerator->generateString(64)); - $application->setClientId($this->randomGenerator->generateString(32, \random::LETTERS_AND_NUMBERS)); - $application->setClientSecret($this->randomGenerator->generateString(32, \random::LETTERS_AND_NUMBERS)); + $application->setClientId($this->randomGenerator->generateString(32, TokenManipulator::LETTERS_AND_NUMBERS)); + $application->setClientSecret($this->randomGenerator->generateString(32, TokenManipulator::LETTERS_AND_NUMBERS)); $this->om->persist($application); $this->om->flush(); diff --git a/lib/Alchemy/Phrasea/Model/Manipulator/ApiOauthCodeManipulator.php b/lib/Alchemy/Phrasea/Model/Manipulator/ApiOauthCodeManipulator.php index fcc89a4ab3..43022679c6 100644 --- a/lib/Alchemy/Phrasea/Model/Manipulator/ApiOauthCodeManipulator.php +++ b/lib/Alchemy/Phrasea/Model/Manipulator/ApiOauthCodeManipulator.php @@ -16,6 +16,7 @@ use Alchemy\Phrasea\Authentication\ACLProvider; use Alchemy\Phrasea\Model\Entities\ApiAccount; use Alchemy\Phrasea\Model\Entities\ApiOauthCode; use Alchemy\Phrasea\Model\Entities\User; +use Alchemy\Phrasea\Model\Manipulator\TokenManipulator; use Doctrine\Common\Persistence\ObjectManager; use Doctrine\ORM\EntityRepository; use RandomLib\Generator; @@ -69,7 +70,7 @@ class ApiOauthCodeManipulator implements ManipulatorInterface private function getNewCode() { do { - $code = $this->randomGenerator->generateString(16, \random::LETTERS_AND_NUMBERS); + $code = $this->randomGenerator->generateString(16, TokenManipulator::LETTERS_AND_NUMBERS); } while (null !== $this->repository->find($code)); return $code; diff --git a/lib/Alchemy/Phrasea/Model/Manipulator/ApiOauthRefreshTokenManipulator.php b/lib/Alchemy/Phrasea/Model/Manipulator/ApiOauthRefreshTokenManipulator.php index 824e063bb5..416888907a 100644 --- a/lib/Alchemy/Phrasea/Model/Manipulator/ApiOauthRefreshTokenManipulator.php +++ b/lib/Alchemy/Phrasea/Model/Manipulator/ApiOauthRefreshTokenManipulator.php @@ -16,6 +16,7 @@ use Alchemy\Phrasea\Authentication\ACLProvider; use Alchemy\Phrasea\Model\Entities\ApiAccount; use Alchemy\Phrasea\Model\Entities\ApiOauthRefreshtoken; use Alchemy\Phrasea\Model\Entities\User; +use Alchemy\Phrasea\Model\Manipulator\TokenManipulator; use Doctrine\Common\Persistence\ObjectManager; use Doctrine\ORM\EntityRepository; use RandomLib\Generator; @@ -68,7 +69,7 @@ class ApiOauthRefreshTokenManipulator implements ManipulatorInterface private function getNewToken() { do { - $refreshToken = $this->randomGenerator->generateString(32, \random::LETTERS_AND_NUMBERS); + $refreshToken = $this->randomGenerator->generateString(32, TokenManipulator::LETTERS_AND_NUMBERS); } while (null !== $this->repository->find($refreshToken)); return $refreshToken; diff --git a/lib/Alchemy/Phrasea/Model/Manipulator/ApiOauthTokenManipulator.php b/lib/Alchemy/Phrasea/Model/Manipulator/ApiOauthTokenManipulator.php index 0ea4be9d3a..7961f7af2f 100644 --- a/lib/Alchemy/Phrasea/Model/Manipulator/ApiOauthTokenManipulator.php +++ b/lib/Alchemy/Phrasea/Model/Manipulator/ApiOauthTokenManipulator.php @@ -17,6 +17,7 @@ use Alchemy\Phrasea\Model\Entities\ApiAccount; use Alchemy\Phrasea\Model\Entities\ApiOauthToken; use Alchemy\Phrasea\Model\Entities\Session; use Alchemy\Phrasea\Model\Entities\User; +use Alchemy\Phrasea\Model\Manipulator\TokenManipulator; use Doctrine\Common\Persistence\ObjectManager; use Doctrine\ORM\EntityRepository; use RandomLib\Generator; @@ -77,7 +78,7 @@ class ApiOauthTokenManipulator implements ManipulatorInterface private function getNewToken() { do { - $token = $this->randomGenerator->generateString(32, \random::LETTERS_AND_NUMBERS); + $token = $this->randomGenerator->generateString(32, TokenManipulator::LETTERS_AND_NUMBERS); } while (null !== $this->repository->find($token)); return $token; From 378857f1682dc17af9915e8b662e669731e18cbc Mon Sep 17 00:00:00 2001 From: Nicolas Le Goff Date: Wed, 5 Mar 2014 18:49:43 +0100 Subject: [PATCH 037/112] Update OAuth2 Controller to use new entities & manipulator --- lib/Alchemy/Phrasea/Controller/Api/Oauth2.php | 64 ++++++++++--------- 1 file changed, 34 insertions(+), 30 deletions(-) diff --git a/lib/Alchemy/Phrasea/Controller/Api/Oauth2.php b/lib/Alchemy/Phrasea/Controller/Api/Oauth2.php index 522f736626..0758ebe8ee 100644 --- a/lib/Alchemy/Phrasea/Controller/Api/Oauth2.php +++ b/lib/Alchemy/Phrasea/Controller/Api/Oauth2.php @@ -21,6 +21,7 @@ use Silex\ControllerProviderInterface; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Exception\HttpException; use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; class Oauth2 implements ControllerProviderInterface { @@ -42,23 +43,25 @@ class Oauth2 implements ControllerProviderInterface */ $authorize_func = function () use ($app) { $request = $app['request']; - $oauth2_adapter = $app['oauth']; + $oauth2Adapter = $app['oauth']; $context = new Context(Context::CONTEXT_OAUTH2_NATIVE); $app['dispatcher']->dispatch(PhraseaEvents::PRE_AUTHENTICATE, new PreAuthenticate($request, $context)); //Check for auth params, send error or redirect if not valid - $params = $oauth2_adapter->getAuthorizationRequestParameters($request); + $params = $oauth2Adapter->getAuthorizationRequestParameters($request); - $app_authorized = false; + $appAuthorized = false; $errorMessage = false; - $client = \API_OAuth2_Application::load_from_client_id($app, $params['client_id']); + if (null === $client = $this->app['repo.api-applications']->findByClientId($params['client_id'])) { + throw new NotFoundHttpException(sprintf('Application with client id %s could not be found', $params['client_id'])); + } - $oauth2_adapter->setClient($client); + $oauth2Adapter->setClient($client); - $action_accept = $request->get("action_accept"); - $action_login = $request->get("action_login"); + $actionAccept = $request->get("action_accept"); + $actionLogin = $request->get("action_login"); $template = "api/auth/end_user_authorization.html.twig"; @@ -76,11 +79,9 @@ class Oauth2 implements ControllerProviderInterface } if (!$app['authentication']->isAuthenticated()) { - if ($action_login !== null) { + if ($actionLogin !== null) { try { - $usr_id = $app['auth.native']->getUsrId($request->get("login"), $request->get("password"), $request); - - if (null === $usr_id) { + if (null === $usrId = $app['auth.native']->getUsrId($request->get("login"), $request->get("password"), $request)) { $app['session']->getFlashBag()->set('error', $app->trans('login::erreur: Erreur d\'authentification')); return $app->redirectPath('oauth2_authorize'); @@ -91,48 +92,51 @@ class Oauth2 implements ControllerProviderInterface return $app->redirectPath('oauth2_authorize', ['error' => 'account-locked']); } - $app['authentication']->openAccount($app['repo.users']->find($usr_id)); + $app['authentication']->openAccount($app['repo.users']->find($usrId)); } - return new Response($app['twig']->render($template, ["auth" => $oauth2_adapter])); + return new Response($app['twig']->render($template, ["auth" => $oauth2Adapter])); } //check if current client is already authorized by current user - $user_auth_clients = \API_OAuth2_Application::load_authorized_app_by_user( - $app - , $app['authentication']->getUser() - ); + $clients = $app['repo.api-applications']-findAuthorizedAppsByUser($app['authentication']->getUser()); - foreach ($user_auth_clients as $auth_client) { - if ($client->get_client_id() == $auth_client->get_client_id()) { - $app_authorized = true; + foreach ($clients as $authClient) { + if ($client->getClientId() == $authClient->getClientId()) { + $appAuthorized = true; + break; } } - $account = $oauth2_adapter->updateAccount($app['authentication']->getUser()); + $account = $oauth2Adapter->updateAccount($app['authentication']->getUser()); - $params['account_id'] = $account->get_id(); + $params['account_id'] = $account->getId(); - if (!$app_authorized && $action_accept === null) { + if (!$appAuthorized && $actionAccept === null) { $params = [ - "auth" => $oauth2_adapter, + "auth" => $oauth2Adapter, "errorMessage" => $errorMessage, ]; return new Response($app['twig']->render($template, $params)); - } elseif (!$app_authorized && $action_accept !== null) { - $app_authorized = (Boolean) $action_accept; - $account->set_revoked(!$app_authorized); + } elseif (!$appAuthorized && $actionAccept !== null) { + $appAuthorized = (Boolean) $actionAccept; + + if ($appAuthorized) { + $app['manipulator.api-account']->authorizeAccess($account); + } else { + $app['manipulator.api-account']->revokeAccess($account); + } } //if native app show template - if ($oauth2_adapter->isNativeApp($params['redirect_uri'])) { - $params = $oauth2_adapter->finishNativeClientAuthorization($app_authorized, $params); + if ($oauth2Adapter->isNativeApp($params['redirect_uri'])) { + $params = $oauth2Adapter->finishNativeClientAuthorization($appAuthorized, $params); return new Response($app['twig']->render("api/auth/native_app_access_token.html.twig", $params)); } - $oauth2_adapter->finishClientAuthorization($app_authorized, $params); + $oauth2Adapter->finishClientAuthorization($appAuthorized, $params); // As OAuth2 library already outputs response content, we need to send an empty // response to avoid breaking silex controller From 2882c11721913c3072a9cf02e26bc3f61fb521db Mon Sep 17 00:00:00 2001 From: Nicolas Le Goff Date: Wed, 5 Mar 2014 18:58:10 +0100 Subject: [PATCH 038/112] Add Api Application Converter --- .../Provider/ConvertersServiceProvider.php | 5 +++ .../Converter/ApiApplicationConverter.php | 40 +++++++++++++++++++ 2 files changed, 45 insertions(+) create mode 100644 lib/Alchemy/Phrasea/Model/Converter/ApiApplicationConverter.php diff --git a/lib/Alchemy/Phrasea/Core/Provider/ConvertersServiceProvider.php b/lib/Alchemy/Phrasea/Core/Provider/ConvertersServiceProvider.php index be04e45f92..213d97242c 100644 --- a/lib/Alchemy/Phrasea/Core/Provider/ConvertersServiceProvider.php +++ b/lib/Alchemy/Phrasea/Core/Provider/ConvertersServiceProvider.php @@ -11,6 +11,7 @@ namespace Alchemy\Phrasea\Core\Provider; +use Alchemy\Phrasea\Model\Converter\ApiApplicationConverter; use Alchemy\Phrasea\Model\Converter\BasketConverter; use Alchemy\Phrasea\Model\Converter\TaskConverter; use Alchemy\Phrasea\Model\Converter\TokenConverter; @@ -32,6 +33,10 @@ class ConvertersServiceProvider implements ServiceProviderInterface $app['converter.token'] = $app->share(function ($app) { return new TokenConverter($app['repo.tokens']); }); + + $app['converter.api-application'] = $app->share(function ($app) { + return new ApiApplicationConverter($app['repo.api-applications']); + }); } public function boot(Application $app) diff --git a/lib/Alchemy/Phrasea/Model/Converter/ApiApplicationConverter.php b/lib/Alchemy/Phrasea/Model/Converter/ApiApplicationConverter.php new file mode 100644 index 0000000000..92ab78c822 --- /dev/null +++ b/lib/Alchemy/Phrasea/Model/Converter/ApiApplicationConverter.php @@ -0,0 +1,40 @@ +repository = $repository; + } + + /** + * {@inheritdoc} + * + * @return ApiApplication + */ + public function convert($id) + { + if (null === $application = $this->$this->repository->find((int) $id)) { + throw new NotFoundHttpException(sprintf('Application %s not found.', $id)); + } + + return $application; + } +} From 782ed07a205992852f2209c9393aabc545e809aa Mon Sep 17 00:00:00 2001 From: Nicolas Le Goff Date: Wed, 5 Mar 2014 19:03:09 +0100 Subject: [PATCH 039/112] Add Api Application middleware provider --- lib/Alchemy/Phrasea/Application.php | 4 ++- .../ApiApplicationMiddlewareProvider.php | 32 +++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 lib/Alchemy/Phrasea/Core/Middleware/ApiApplicationMiddlewareProvider.php diff --git a/lib/Alchemy/Phrasea/Application.php b/lib/Alchemy/Phrasea/Application.php index 5c3dfdffe0..7b8407d997 100644 --- a/lib/Alchemy/Phrasea/Application.php +++ b/lib/Alchemy/Phrasea/Application.php @@ -72,13 +72,14 @@ use Alchemy\Phrasea\Controller\Utils\ConnectionTest; use Alchemy\Phrasea\Controller\Utils\PathFileTest; use Alchemy\Phrasea\Controller\User\Notifications; use Alchemy\Phrasea\Controller\User\Preferences; -use Alchemy\Phrasea\Core\Middleware\TokenMiddlewareProvider; use Alchemy\Phrasea\Core\PhraseaExceptionHandler; use Alchemy\Phrasea\Core\Event\Subscriber\LogoutSubscriber; use Alchemy\Phrasea\Core\Event\Subscriber\PhraseaLocaleSubscriber; use Alchemy\Phrasea\Core\Event\Subscriber\MaintenanceSubscriber; use Alchemy\Phrasea\Core\Event\Subscriber\CookiesDisablerSubscriber; +use Alchemy\Phrasea\Core\Middleware\ApiApplicationMiddlewareProvider; use Alchemy\Phrasea\Core\Middleware\BasketMiddlewareProvider; +use Alchemy\Phrasea\Core\Middleware\TokenMiddlewareProvider; use Alchemy\Phrasea\Core\Provider\ACLServiceProvider; use Alchemy\Phrasea\Core\Provider\AuthenticationManagerServiceProvider; use Alchemy\Phrasea\Core\Provider\BrowserServiceProvider; @@ -213,6 +214,7 @@ class Application extends SilexApplication $this->register(new BasketMiddlewareProvider()); $this->register(new TokenMiddlewareProvider()); + $this->register(new ApiApplicationMiddlewareProvider()); $this->register(new ACLServiceProvider()); $this->register(new AuthenticationManagerServiceProvider()); diff --git a/lib/Alchemy/Phrasea/Core/Middleware/ApiApplicationMiddlewareProvider.php b/lib/Alchemy/Phrasea/Core/Middleware/ApiApplicationMiddlewareProvider.php new file mode 100644 index 0000000000..de81886275 --- /dev/null +++ b/lib/Alchemy/Phrasea/Core/Middleware/ApiApplicationMiddlewareProvider.php @@ -0,0 +1,32 @@ +protect(function (Request $request, Application $app) { + if ($request->attributes->has('application_id')) { + $request->attributes->set('application_id', $app['converter.api-application']->convert($request->attributes->get('application_id'))); + } + }); + } + + public function boot(Application $app) + { + } +} From 05f71adf4b299033783df5fe71d88bd753031e1e Mon Sep 17 00:00:00 2001 From: Nicolas Le Goff Date: Wed, 5 Mar 2014 19:20:21 +0100 Subject: [PATCH 040/112] Rename attribut to 'application' --- .../Core/Middleware/ApiApplicationMiddlewareProvider.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/Alchemy/Phrasea/Core/Middleware/ApiApplicationMiddlewareProvider.php b/lib/Alchemy/Phrasea/Core/Middleware/ApiApplicationMiddlewareProvider.php index de81886275..7c7e7941b1 100644 --- a/lib/Alchemy/Phrasea/Core/Middleware/ApiApplicationMiddlewareProvider.php +++ b/lib/Alchemy/Phrasea/Core/Middleware/ApiApplicationMiddlewareProvider.php @@ -20,8 +20,8 @@ class ApiApplicationMiddlewareProvider implements ServiceProviderInterface public function register(Application $app) { $app['middleware.api-application.converter'] = $app->protect(function (Request $request, Application $app) { - if ($request->attributes->has('application_id')) { - $request->attributes->set('application_id', $app['converter.api-application']->convert($request->attributes->get('application_id'))); + if ($request->attributes->has('application')) { + $request->attributes->set('application', $app['converter.api-application']->convert($request->attributes->get('application'))); } }); } From 3ecdd4306b22ff15337c990da246efccdd2f4ea1 Mon Sep 17 00:00:00 2001 From: Nicolas Le Goff Date: Wed, 5 Mar 2014 19:44:06 +0100 Subject: [PATCH 041/112] Sets relation beetwen token and account entity to One-To-One Bi-Directional --- .../Phrasea/Model/Entities/ApiAccount.php | 36 +++++++++++++++++++ .../Phrasea/Model/Entities/ApiOauthToken.php | 3 +- 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/lib/Alchemy/Phrasea/Model/Entities/ApiAccount.php b/lib/Alchemy/Phrasea/Model/Entities/ApiAccount.php index a942242e39..00f0cc98be 100644 --- a/lib/Alchemy/Phrasea/Model/Entities/ApiAccount.php +++ b/lib/Alchemy/Phrasea/Model/Entities/ApiAccount.php @@ -48,6 +48,14 @@ class ApiAccount **/ private $application; + /** + * @ORM\OneToOne(targetEntity="ApiOauthToken", inversedBy="account") + * @ORM\JoinColumn(name="oauth_token", referencedColumnName="id", nullable=true) + * + * @return ApiApplication + **/ + private $oauthToken; + /** * @Gedmo\Timestampable(on="create") * @ORM\Column(type="datetime") @@ -161,4 +169,32 @@ class ApiAccount { return $this->user; } + + /** + * @param ApiOauthToken $oauthToken + * + * @return ApiAccount + */ + public function setOauthToken(ApiOauthToken $oauthToken) + { + $this->oauthToken = $oauthToken; + + return $this; + } + + /** + * @return ApiOauthToken + */ + public function getOauthToken() + { + return $this->oauthToken; + } + + /** + * @return boolean + */ + public function hasOauthToken() + { + return null !== $this->oauthToken; + } } diff --git a/lib/Alchemy/Phrasea/Model/Entities/ApiOauthToken.php b/lib/Alchemy/Phrasea/Model/Entities/ApiOauthToken.php index 93603c907d..df8f1ba71e 100644 --- a/lib/Alchemy/Phrasea/Model/Entities/ApiOauthToken.php +++ b/lib/Alchemy/Phrasea/Model/Entities/ApiOauthToken.php @@ -27,8 +27,7 @@ class ApiOauthToken private $session; /** - * @ORM\ManyToOne(targetEntity="ApiAccount") - * @ORM\JoinColumn(name="account_id", referencedColumnName="id", nullable=false) + * @OneToOne(targetEntity="ApiAccount", mappedBy="oauthToken", nullable=false) * * @return ApiAccount **/ From 53dda0b09e137ced6d60fb5bf844e46ef3ba13e4 Mon Sep 17 00:00:00 2001 From: Nicolas Le Goff Date: Wed, 5 Mar 2014 20:50:19 +0100 Subject: [PATCH 042/112] Delete references to API_OAuth2_Application class --- .../Command/Developer/RegenerateSqliteDb.php | 29 ++- .../Phrasea/Controller/Root/Account.php | 35 ++-- .../Phrasea/Controller/Root/Developers.php | 180 ++++++++---------- lib/classes/API/OAuth2/Form/DevAppDesktop.php | 9 +- .../API/OAuth2/Form/DevAppInternet.php | 11 +- lib/classes/patch/370alpha3a.php | 25 +-- lib/classes/patch/3715alpha1a.php | 25 +-- .../web/developers/application.html.twig | 2 +- .../Phrasea/Controller/Api/ApiTestCase.php | 12 +- .../Phrasea/Controller/Api/OAuth2Test.php | 30 +-- .../Controller/Root/DevelopersTest.php | 33 ++-- tests/classes/PhraseanetTestCase.php | 4 +- tests/classes/api/oauthv2/AccountTest.php | 2 +- tests/classes/api/oauthv2/ApplicationTest.php | 111 +++++------ 14 files changed, 238 insertions(+), 270 deletions(-) diff --git a/lib/Alchemy/Phrasea/Command/Developer/RegenerateSqliteDb.php b/lib/Alchemy/Phrasea/Command/Developer/RegenerateSqliteDb.php index 6da4cb6540..b316ada53b 100644 --- a/lib/Alchemy/Phrasea/Command/Developer/RegenerateSqliteDb.php +++ b/lib/Alchemy/Phrasea/Command/Developer/RegenerateSqliteDb.php @@ -13,6 +13,7 @@ namespace Alchemy\Phrasea\Command\Developer; use Alchemy\Phrasea\Border\Manager; use Alchemy\Phrasea\Command\Command; +use Alchemy\Phrasea\Model\Entities\ApiApplication; use Alchemy\Phrasea\Model\Entities\AuthFailure; use Alchemy\Phrasea\Model\Entities\AggregateToken; use Alchemy\Phrasea\Model\Entities\Basket; @@ -124,8 +125,8 @@ class RegenerateSqliteDb extends Command $fixtures['user']['test_phpunit_alt2'] = $DI['user_alt2']->getId(); $fixtures['user']['user_guest'] = $DI['user_guest']->getId(); - $fixtures['oauth']['user'] = $DI['app-user']->get_id(); - $fixtures['oauth']['user_notAdmin'] = $DI['app-user_notAdmin']->get_id(); + $fixtures['oauth']['user'] = $DI['api-app-user']->getId(); + $fixtures['oauth']['user-not-admin'] = $DI['api-app-user-not-admin']->getId(); $fixtures['databox']['records'] = $DI['databox']->get_sbas_id(); $fixtures['collection']['coll'] = $DI['coll']->get_base_id(); @@ -182,15 +183,23 @@ class RegenerateSqliteDb extends Command private function insertOauthApps(\Pimple $DI) { - $DI['app-user'] = \API_OAuth2_Application::create($this->container, $DI['user'], 'test application for user'); - $DI['app-user']->set_redirect_uri('http://callback.com/callback/'); - $DI['app-user']->set_website('http://website.com/'); - $DI['app-user']->set_type(\API_OAuth2_Application::WEB_TYPE); + $DI['api-app-user'] = $this->container['manipulator.api-application']->create( + 'test application for user', + ApiApplication::WEB_TYPE, + 'an api application description', + 'http://website.com/', + $DI['user'], + 'http://callback.com/callback/' + ); - $DI['app-user_notAdmin'] = \API_OAuth2_Application::create($this->container, $DI['user_notAdmin'], 'test application for user not admin'); - $DI['app-user_notAdmin']->set_redirect_uri('http://callback.com/callback/'); - $DI['app-user_notAdmin']->set_website('http://website.com/'); - $DI['app-user_notAdmin']->set_type(\API_OAuth2_Application::WEB_TYPE); + $DI['api-app-user-not-admin'] = $this->container['manipulator.api-application']->create( + 'test application for user', + ApiApplication::WEB_TYPE, + 'an api application description', + 'http://website.com/', + $DI['user_notAdmin'], + 'http://callback.com/callback/' + ); } private function insertAuthFailures(EntityManager $em, \Pimple $DI) diff --git a/lib/Alchemy/Phrasea/Controller/Root/Account.php b/lib/Alchemy/Phrasea/Controller/Root/Account.php index 7a259eb660..9e24e87e61 100644 --- a/lib/Alchemy/Phrasea/Controller/Root/Account.php +++ b/lib/Alchemy/Phrasea/Controller/Root/Account.php @@ -69,7 +69,8 @@ class Account implements ControllerProviderInterface ->bind('account_auth_apps'); // Displays a an authorized app grant - $controllers->get('/security/application/{application_id}/grant/', 'account.controller:grantAccess') + $controllers->get('/security/application/{application}/grant/', 'account.controller:grantAccess') + ->before($app['middleware.api-application.converter']) ->assert('application_id', '\d+') ->bind('grant_app_access'); @@ -191,33 +192,29 @@ class Account implements ControllerProviderInterface /** * Display authorized applications that can access user informations * - * @param Application $app A Silex application where the controller is mounted on - * @param Request $request The current request - * @param Integer $application_id The application id + * @param Application $app + * @param Request $request + * @param ApiApplication $application * * @return JsonResponse */ - public function grantAccess(Application $app, Request $request, $application_id) + public function grantAccess(Application $app, Request $request, ApiApplication $application) { if (!$request->isXmlHttpRequest() || !array_key_exists($request->getMimeType('json'), array_flip($request->getAcceptableContentTypes()))) { $app->abort(400, $app->trans('Bad request format, only JSON is allowed')); } - $error = false; - - try { - $account = \API_OAuth2_Account::load_with_user( - $app - , new \API_OAuth2_Application($app, $application_id) - , $app['authentication']->getUser() - ); - - $account->set_revoked((bool) $request->query->get('revoke'), false); - } catch (NotFoundHttpException $e) { - $error = true; + if (null === $account = $app['repo.api-accounts']->findByUserAndApplication($app['authentication']->getUser(), $application)) { + return $app->json(['success' => false]); } - return $app->json(['success' => !$error]); + if ((Boolean) $request->query->get('revoke')) { + $app['manipulator.api-account']->authorizeAccess($account); + } else { + $app['manipulator.api-account']->revokeAccess($account); + } + + return $app->json(['success' => true]); } /** @@ -244,7 +241,7 @@ class Account implements ControllerProviderInterface public function accountAuthorizedApps(Application $app, Request $request) { return $app['twig']->render('account/authorized_apps.html.twig', [ - "applications" => \API_OAuth2_Application::load_app_by_user($app, $app['authentication']->getUser()), + "applications" => $app['repo.api-applications']->findByUser($app['authentication']->getUser()), ]); } diff --git a/lib/Alchemy/Phrasea/Controller/Root/Developers.php b/lib/Alchemy/Phrasea/Controller/Root/Developers.php index bbc4e94398..34f241b3e6 100644 --- a/lib/Alchemy/Phrasea/Controller/Root/Developers.php +++ b/lib/Alchemy/Phrasea/Controller/Root/Developers.php @@ -11,6 +11,8 @@ namespace Alchemy\Phrasea\Controller\Root; +use Alchemy\Phrasea\Exception\InvalidArgumentException; +use Alchemy\Phrasea\Model\Entities\ApiApplication; use Silex\Application; use Silex\ControllerProviderInterface; use Symfony\Component\HttpFoundation\JsonResponse; @@ -37,23 +39,28 @@ class Developers implements ControllerProviderInterface $controllers->post('/application/', 'controller.account.developers:newApp') ->bind('submit_developers_application'); - $controllers->get('/application/{id}/', 'controller.account.developers:getApp') + $controllers->get('/application/{application}/', 'controller.account.developers:getApp') + ->before($app['middleware.api-application.converter']) ->assert('id', '\d+') ->bind('developers_application'); - $controllers->delete('/application/{id}/', 'controller.account.developers:deleteApp') + $controllers->delete('/application/{application}/', 'controller.account.developers:deleteApp') + ->before($app['middleware.api-application.converter']) ->assert('id', '\d+') ->bind('delete_developers_application'); - $controllers->post('/application/{id}/authorize_grant_password/', 'controller.account.developers:authorizeGrantpassword') + $controllers->post('/application/{application}/authorize_grant_password/', 'controller.account.developers:authorizeGrantPassword') + ->before($app['middleware.api-application.converter']) ->assert('id', '\d+') ->bind('submit_developers_application_authorize_grant_password'); - $controllers->post('/application/{id}/access_token/', 'controller.account.developers:renewAccessToken') + $controllers->post('/application/{application}/access_token/', 'controller.account.developers:renewAccessToken') + ->before($app['middleware.api-application.converter']) ->assert('id', '\d+') ->bind('submit_developers_application_token'); - $controllers->post('/application/{id}/callback/', 'controller.account.developers:renewAppCallback') + $controllers->post('/application/{application}/callback/', 'controller.account.developers:renewAppCallback') + ->before($app['middleware.api-application.converter']) ->assert('id', '\d+') ->bind('submit_application_callback'); @@ -61,123 +68,97 @@ class Developers implements ControllerProviderInterface } /** - * Delete application + * Delete application. + * + * @param Application $app + * @param Request $request + * @param ApiApplication $application * - * @param Application $app A Silex application where the controller is mounted on - * @param Request $request The current request - * @param integer $id The application id * @return JsonResponse */ - public function deleteApp(Application $app, Request $request, $id) + public function deleteApp(Application $app, Request $request, ApiApplication $application) { if (!$request->isXmlHttpRequest() || !array_key_exists($request->getMimeType('json'), array_flip($request->getAcceptableContentTypes()))) { $app->abort(400, 'Bad request format, only JSON is allowed'); } - $error = false; + $app['manipulator.api-application']->delete($application); - try { - $clientApp = new \API_OAuth2_Application($app, $id); - $clientApp->delete(); - } catch (NotFoundHttpException $e) { - $error = true; - } - - return $app->json(['success' => !$error]); + return $app->json(['success' => true]); } /** - * Change application callback + * Change application callback. + * + * @param Application $app + * @param Request $request + * @param ApiApplication $application * - * @param Application $app A Silex application where the controller is mounted on - * @param Request $request The current request - * @param integer $id The application id * @return JsonResponse */ - public function renewAppCallback(Application $app, Request $request, $id) + public function renewAppCallback(Application $app, Request $request, ApiApplication $application) { if (!$request->isXmlHttpRequest() || !array_key_exists($request->getMimeType('json'), array_flip($request->getAcceptableContentTypes()))) { $app->abort(400, 'Bad request format, only JSON is allowed'); } - $error = false; - try { - $clientApp = new \API_OAuth2_Application($app, $id); - - if (null !== $request->request->get("callback")) { - $clientApp->set_redirect_uri($request->request->get("callback")); - } else { - $error = true; - } - } catch (NotFoundHttpException $e) { - $error = true; + $app['manipulator.api-application']->setRedirectUri($request->request->get("callback")); + } catch (InvalidArgumentException $e) { + return $app->json(['success' => false]); } - return $app->json(['success' => !$error]); + return $app->json(['success' => true]); } /** - * Authorize application to use a grant password type + * Authorize application to use a grant password type. + * + * @param Application $app + * @param Request $request + * @param ApiApplication $application * - * @param Application $app A Silex application where the controller is mounted on - * @param Request $request The current request - * @param integer $id The application id * @return JsonResponse */ - public function renewAccessToken(Application $app, Request $request, $id) + public function renewAccessToken(Application $app, Request $request, ApiApplication $application) { if (!$request->isXmlHttpRequest() || !array_key_exists($request->getMimeType('json'), array_flip($request->getAcceptableContentTypes()))) { $app->abort(400, 'Bad request format, only JSON is allowed'); } - $error = false; - $accessToken = null; - - try { - $clientApp = new \API_OAuth2_Application($app, $id); - $account = $clientApp->get_user_account($app['authentication']->getUser()); - - $token = $account->get_token(); - - if ($token instanceof \API_OAuth2_Token) { - $token->renew(); - } else { - $token = \API_OAuth2_Token::create($app['phraseanet.appbox'], $account, $app['random.medium']); - } - - $accessToken = $token->get_value(); - } catch (\Exception $e) { - $error = true; + if (null === $account = $app['repo.api-accounts']->findByUserAndApplication($app['authentication']->getUser(), $application)) { + $app->abort(404, sprintf('Account not found for application %s', $application->getName())); } - return $app->json(['success' => !$error, 'token' => $accessToken]); + $token = $account->getOauthToken(); + if ($account->hasOauthToken()) { + $app['manipulator.api-oauth-token']->renew($token); + } else { + $token = $app['manipulator.api-oauth-token']->create($account); + } + + return $app->json(['success' => true, 'token' => $token->getOauthToken()]); } /** - * Authorize application to use a grant password type + * Authorize application to use a grant password type. + * + * @param Application $app + * @param Request $request + * @param ApiApplication $application * - * @param Application $app A Silex application where the controller is mounted on - * @param Request $request The current request - * @param integer $id The application id * @return JsonResponse */ - public function authorizeGrantpassword(Application $app, Request $request, $id) + public function authorizeGrantPassword(Application $app, Request $request, ApiApplication $application) { if (!$request->isXmlHttpRequest() || !array_key_exists($request->getMimeType('json'), array_flip($request->getAcceptableContentTypes()))) { $app->abort(400, 'Bad request format, only JSON is allowed'); } - $error = false; + $application->setGrantPassword((Boolean) $request->request->get('grant')); + $app['manipulator.api-application']->update($application); - try { - $clientApp = new \API_OAuth2_Application($app, $id); - $clientApp->set_grant_password((bool) $request->request->get('grant', false)); - } catch (NotFoundHttpException $e) { - $error = true; - } - - return $app->json(['success' => !$error]); + return $app->json(['success' => true]); } /** @@ -189,7 +170,7 @@ class Developers implements ControllerProviderInterface */ public function newApp(Application $app, Request $request) { - if ($request->request->get('type') === \API_OAuth2_Application::DESKTOP_TYPE) { + if ($request->request->get('type') === ApiApplication::DESKTOP_TYPE) { $form = new \API_OAuth2_Form_DevAppDesktop($app['request']); } else { $form = new \API_OAuth2_Form_DevAppInternet($app['request']); @@ -198,22 +179,22 @@ class Developers implements ControllerProviderInterface $violations = $app['validator']->validate($form); if ($violations->count() === 0) { - $application = \API_OAuth2_Application::create($app, $app['authentication']->getUser(), $form->getName()); - $application - ->set_description($form->getDescription()) - ->set_redirect_uri($form->getSchemeCallback() . $form->getCallback()) - ->set_type($form->getType()) - ->set_website($form->getSchemeWebsite() . $form->getWebsite()); + $application = $app['manipulator.api-application']->create( + $form->getName(), + $form->getType(), + $form->getDescription(), + sprintf('%s%s', $form->getSchemeWebsite(), $form->getWebsite()), + $app['authentication']->getUser(), + sprintf('%s%s', $form->getSchemeCallback(), $form->getCallback()) + ); return $app->redirectPath('developers_application', ['id' => $application->get_id()]); } - $var = [ + return $app['twig']->render('/developers/application_form.html.twig', [ "violations" => $violations, "form" => $form - ]; - - return $app['twig']->render('/developers/application_form.html.twig', $var); + ]); } /** @@ -226,7 +207,7 @@ class Developers implements ControllerProviderInterface public function listApps(Application $app, Request $request) { return $app['twig']->render('developers/applications.html.twig', [ - "applications" => \API_OAuth2_Application::load_dev_app_by_user($app, $app['authentication']->getUser()) + "applications" => $app['repo.api-applications']->findByCreator($app['authentication']->getUser()) ]); } @@ -247,25 +228,26 @@ class Developers implements ControllerProviderInterface } /** - * Get application information + * Gets application information. * - * @param Application $app A Silex application where the controller is mounted on - * @param Request $request The current request - * @param integer $id The application id - * @return Response + * @param Application $app + * @param Request $request + * @param ApiApplication $application + * + * @return mixed */ - public function getApp(Application $app, Request $request, $id) + public function getApp(Application $app, Request $request, ApiApplication $application) { - try { - $client = new \API_OAuth2_Application($app, $id); - } catch (NotFoundHttpException $e) { - $app->abort(404); + $token = null; + + if (null !== $account = $app['repo.api-accounts']->findByUserAndApplication($app['authentication']->getUser(), $application)) { + if ($account->hasOauthToken()) { + $token = $account->getOauthToken()->getOauthToken(); + } } - $token = $client->get_user_account($app['authentication']->getUser())->get_token()->get_value(); - return $app['twig']->render('developers/application.html.twig', [ - "application" => $client, + "application" => $application, "user" => $app['authentication']->getUser(), "token" => $token ]); diff --git a/lib/classes/API/OAuth2/Form/DevAppDesktop.php b/lib/classes/API/OAuth2/Form/DevAppDesktop.php index 5f2faeddda..7b50727b71 100644 --- a/lib/classes/API/OAuth2/Form/DevAppDesktop.php +++ b/lib/classes/API/OAuth2/Form/DevAppDesktop.php @@ -9,6 +9,7 @@ * file that was distributed with this source code. */ +use Alchemy\Phrasea\Model\Entities\ApiApplication; use Symfony\Component\Validator\Mapping\ClassMetadata; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\Validator\Constraints; @@ -48,9 +49,7 @@ class API_OAuth2_Form_DevAppDesktop public $urlwebsite; /** - * - * @param Request $request - * @return API_OAuth2_Form_DevApp + * @param Request $request */ public function __construct(Request $request) { @@ -58,8 +57,8 @@ class API_OAuth2_Form_DevAppDesktop $this->description = $request->get('description', ''); $this->scheme_website = $request->get('scheme-website', 'http://'); $this->website = $request->get('website', ''); - $this->callback = API_OAuth2_Application::NATIVE_APP_REDIRECT_URI; - $this->type = API_OAuth2_Application::DESKTOP_TYPE; + $this->callback = ApiApplication::NATIVE_APP_REDIRECT_URI; + $this->type = ApiApplication::DESKTOP_TYPE; $this->urlwebsite = $this->scheme_website . $this->website; diff --git a/lib/classes/API/OAuth2/Form/DevAppInternet.php b/lib/classes/API/OAuth2/Form/DevAppInternet.php index e73dc077c8..e05fa297b3 100644 --- a/lib/classes/API/OAuth2/Form/DevAppInternet.php +++ b/lib/classes/API/OAuth2/Form/DevAppInternet.php @@ -9,6 +9,7 @@ * file that was distributed with this source code. */ +use Alchemy\Phrasea\Model\Entities\ApiApplication; use Symfony\Component\Validator\Mapping\ClassMetadata; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\Validator\Constraints; @@ -44,9 +45,7 @@ class API_OAuth2_Form_DevAppInternet public $urlcallback; /** - * - * @param Request $request - * @return API_OAuth2_Form_DevApp + * @param Request $request */ public function __construct(Request $request) { @@ -56,10 +55,10 @@ class API_OAuth2_Form_DevAppInternet $this->callback = $request->get('callback', ''); $this->scheme_website = $request->get('scheme-website', 'http://'); $this->scheme_callback = $request->get('scheme-callback', 'http://'); - $this->type = API_OAuth2_Application::WEB_TYPE; + $this->type = ApiApplication::WEB_TYPE; - $this->urlwebsite = $this->scheme_website . $this->website; - $this->urlcallback = $this->scheme_callback . $this->callback; + $this->urlwebsite = sprintf('%s%s', $this->scheme_website, $this->website); + $this->urlcallback = sprintf('%s%s', $this->scheme_callback, $this->callback); return $this; } diff --git a/lib/classes/patch/370alpha3a.php b/lib/classes/patch/370alpha3a.php index 9adf908a2d..f8c1bc8561 100644 --- a/lib/classes/patch/370alpha3a.php +++ b/lib/classes/patch/370alpha3a.php @@ -10,6 +10,7 @@ */ use Alchemy\Phrasea\Application; +use Alchemy\Phrasea\Model\Entities\ApiApplication; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; class patch_370alpha3a extends patchAbstract @@ -58,18 +59,20 @@ class patch_370alpha3a extends patchAbstract */ public function apply(base $appbox, Application $app) { - try { - \API_OAuth2_Application::load_from_client_id($app, \API_OAuth2_Application_Navigator::CLIENT_ID); - } catch (NotFoundHttpException $e) { - $client = \API_OAuth2_Application::create($app, null, \API_OAuth2_Application_Navigator::CLIENT_NAME); + if (null === $app['repo.api-applications']->findByClientId(\API_OAuth2_Application_Navigator::CLIENT_ID)) { + $application = $app['manipulator.api-applications']->create( + \API_OAuth2_Application_Navigator::CLIENT_NAME, + ApiApplication::DESKTOP_TYPE, + 'http://www.phraseanet.com', + null, + ApiApplication::NATIVE_APP_REDIRECT_URI + ); - $client->set_activated(true); - $client->set_grant_password(true); - $client->set_website("http://www.phraseanet.com"); - $client->set_client_id(\API_OAuth2_Application_Navigator::CLIENT_ID); - $client->set_client_secret(\API_OAuth2_Application_Navigator::CLIENT_SECRET); - $client->set_type(\API_OAuth2_Application::DESKTOP_TYPE); - $client->set_redirect_uri(\API_OAuth2_Application::NATIVE_APP_REDIRECT_URI); + $application->setGrantPassword(true); + $application->setClientId(\API_OAuth2_Application_Navigator::CLIENT_ID); + $application->setClientSecret(\API_OAuth2_Application_Navigator::CLIENT_SECRET); + + $app['manipulator.api-applications']->update($application); } return true; diff --git a/lib/classes/patch/3715alpha1a.php b/lib/classes/patch/3715alpha1a.php index d9556d69c3..134b0ce565 100644 --- a/lib/classes/patch/3715alpha1a.php +++ b/lib/classes/patch/3715alpha1a.php @@ -10,6 +10,7 @@ */ use Alchemy\Phrasea\Application; +use Alchemy\Phrasea\Model\Entities\ApiApplication; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; class patch_3715alpha1a extends patchAbstract @@ -59,18 +60,20 @@ class patch_3715alpha1a extends patchAbstract */ public function apply(base $appbox, Application $app) { - try { - \API_OAuth2_Application::load_from_client_id($app, \API_OAuth2_Application_OfficePlugin::CLIENT_ID); - } catch (NotFoundHttpException $e) { - $client = \API_OAuth2_Application::create($app, null, \API_OAuth2_Application_OfficePlugin::CLIENT_NAME); + if (null === $app['repo.api-applications']->findByClientId(\API_OAuth2_Application_OfficePlugin::CLIENT_ID)) { + $application = $app['manipulator.api-applications']->create( + \API_OAuth2_Application_OfficePlugin::CLIENT_NAME, + ApiApplication::DESKTOP_TYPE, + 'http://www.phraseanet.com', + null, + ApiApplication::NATIVE_APP_REDIRECT_URI + ); - $client->set_activated(true); - $client->set_grant_password(true); - $client->set_website("http://www.phraseanet.com"); - $client->set_client_id(\API_OAuth2_Application_OfficePlugin::CLIENT_ID); - $client->set_client_secret(\API_OAuth2_Application_OfficePlugin::CLIENT_SECRET); - $client->set_type(\API_OAuth2_Application::DESKTOP_TYPE); - $client->set_redirect_uri(\API_OAuth2_Application::NATIVE_APP_REDIRECT_URI); + $application->setGrantPassword(true); + $application->setClientId(\API_OAuth2_Application_OfficePlugin::CLIENT_ID); + $application->setClientSecret(\API_OAuth2_Application_OfficePlugin::CLIENT_SECRET); + + $app['manipulator.api-applications']->update($application); } return true; diff --git a/templates/web/developers/application.html.twig b/templates/web/developers/application.html.twig index e3d6266e2e..e9eac5c9d0 100644 --- a/templates/web/developers/application.html.twig +++ b/templates/web/developers/application.html.twig @@ -32,7 +32,7 @@ {{ "URL de callback" | trans }} - {% if application.get_type() == constant("API_OAuth2_Application::DESKTOP_TYPE") %} + {% if application.get_type() == constant("Alchemy\Phrasea\Model\Entities\ApiApplication::DESKTOP_TYPE") %} {{ application.get_redirect_uri() }} diff --git a/tests/Alchemy/Tests/Phrasea/Controller/Api/ApiTestCase.php b/tests/Alchemy/Tests/Phrasea/Controller/Api/ApiTestCase.php index 64ce5e1beb..1b78483f55 100644 --- a/tests/Alchemy/Tests/Phrasea/Controller/Api/ApiTestCase.php +++ b/tests/Alchemy/Tests/Phrasea/Controller/Api/ApiTestCase.php @@ -7,6 +7,7 @@ use Alchemy\Phrasea\Border\File; use Alchemy\Phrasea\Controller\Api\V1; use Alchemy\Phrasea\Core\PhraseaEvents; use Alchemy\Phrasea\Authentication\Context; +use Alchemy\Phrasea\Model\Entities\ApiApplication; use Alchemy\Phrasea\Model\Entities\Task; use Alchemy\Phrasea\Model\Entities\User; use Doctrine\Common\Collections\ArrayCollection; @@ -27,7 +28,7 @@ abstract class ApiTestCase extends \PhraseanetWebTestCase */ private static $account; /** - * @var \API_OAuth2_Application + * @var ApiApplication */ private static $oauthApplication; /** @@ -39,7 +40,7 @@ abstract class ApiTestCase extends \PhraseanetWebTestCase */ private static $adminAccount; /** - * @var \API_OAuth2_Application + * @var \ApiApplication */ private static $adminApplication; private static $apiInitialized = false; @@ -167,9 +168,10 @@ abstract class ApiTestCase extends \PhraseanetWebTestCase $fail = null; try { - - $nativeApp = \API_OAuth2_Application::load_from_client_id(self::$DI['app'], \API_OAuth2_Application_Navigator::CLIENT_ID); - + $nativeApp = self::$DI['app']['repo.api-applications']->findByClientId(\API_OAuth2_Application_Navigator::CLIENT_ID); + if (null === $nativeApp) { + throw new \Exception(sprintf('%s not found', \API_OAuth2_Application_Navigator::CLIENT_ID)); + } $account = \API_OAuth2_Account::create(self::$DI['app'], self::$DI['user'], $nativeApp); $token = $account->get_token()->get_value(); $this->setToken($token); diff --git a/tests/Alchemy/Tests/Phrasea/Controller/Api/OAuth2Test.php b/tests/Alchemy/Tests/Phrasea/Controller/Api/OAuth2Test.php index 22381f3387..cc8465a7e3 100644 --- a/tests/Alchemy/Tests/Phrasea/Controller/Api/OAuth2Test.php +++ b/tests/Alchemy/Tests/Phrasea/Controller/Api/OAuth2Test.php @@ -4,6 +4,7 @@ namespace Alchemy\Tests\Phrasea\Controller\Api; use Alchemy\Phrasea\Core\PhraseaEvents; use Alchemy\Phrasea\Authentication\Context; +use Alchemy\Phrasea\Model\Entities\ApiApplication; /** * Test oauthv2 flow based on ietf authv2 spec @@ -13,7 +14,7 @@ class OAuth2Test extends \PhraseanetAuthenticatedWebTestCase { /** * - * @var API_OAuth2_Application + * @var ApiApplication */ public static $account_id; public static $account; @@ -44,26 +45,9 @@ class OAuth2Test extends \PhraseanetAuthenticatedWebTestCase parent::tearDownAfterClass(); } - public static function deleteInsertedRow(\appbox $appbox, \API_OAuth2_Application $app) + public static function deleteInsertedRow(\appbox $appbox, ApiApplication $application) { - $conn = $appbox->get_connection(); - $sql = ' - DELETE FROM api_applications - WHERE application_id = :id - '; - $t = [':id' => $app->get_id()]; - $stmt = $conn->prepare($sql); - $stmt->execute($t); - $stmt->closeCursor(); - $sql = ' - DELETE FROM api_accounts - WHERE api_account_id = :id - '; - $acc = self::getAccount(); - $t = [':id' => $acc->get_id()]; - $stmt = $conn->prepare($sql); - $stmt->execute($t); - $stmt->closeCursor(); + self::$DI['app']['manipulator.api-application']->delete($application); } /** @@ -136,11 +120,9 @@ class OAuth2Test extends \PhraseanetAuthenticatedWebTestCase public function testAuthorizeRedirect() { //session off - $apps = \API_OAuth2_Application::load_authorized_app_by_user(self::$DI['app'], self::$DI['user']); + $apps = self::$DI['app']['repos.api-application']->findAuthorizedAppsByUser(self::$DI['user']); foreach ($apps as $app) { - if ($app->get_client_id() == self::$DI['oauth2-app-user']->get_client_id()) { - $authorize = true; - + if ($app->get_client_id() === self::$DI['oauth2-app-user']->getClientId()) { self::$DI['client']->followRedirects(); } } diff --git a/tests/Alchemy/Tests/Phrasea/Controller/Root/DevelopersTest.php b/tests/Alchemy/Tests/Phrasea/Controller/Root/DevelopersTest.php index 8d1d86f390..942af2e579 100644 --- a/tests/Alchemy/Tests/Phrasea/Controller/Root/DevelopersTest.php +++ b/tests/Alchemy/Tests/Phrasea/Controller/Root/DevelopersTest.php @@ -2,6 +2,7 @@ namespace Alchemy\Tests\Phrasea\Controller\Root; +use Alchemy\Phrasea\Model\Entities\ApiApplication; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; class DevelopersTest extends \PhraseanetAuthenticatedWebTestCase @@ -34,7 +35,7 @@ class DevelopersTest extends \PhraseanetAuthenticatedWebTestCase public function testPostNewAppInvalidArguments() { $crawler = self::$DI['client']->request('POST', '/developers/application/', [ - 'type' => \API_OAuth2_Application::WEB_TYPE, + 'type' => ApiApplication::WEB_TYPE, 'name' => '', 'description' => 'okok', 'website' => 'my.website.com', @@ -55,11 +56,11 @@ class DevelopersTest extends \PhraseanetAuthenticatedWebTestCase */ public function testPostNewApp() { - $apps = \API_OAuth2_Application::load_dev_app_by_user(self::$DI['app'], self::$DI['user']); + $apps = self::$DI['app']['repos.api-applications']->findByCreator(self::$DI['user']); $nbApp = count($apps); self::$DI['client']->request('POST', '/developers/application/', [ - 'type' => \API_OAuth2_Application::WEB_TYPE, + 'type' => ApiApplication::WEB_TYPE, 'name' => 'hello', 'description' => 'okok', 'website' => 'my.website.com', @@ -68,7 +69,7 @@ class DevelopersTest extends \PhraseanetAuthenticatedWebTestCase 'scheme-callback' => 'http://' ]); - $apps = \API_OAuth2_Application::load_dev_app_by_user(self::$DI['app'], self::$DI['user']); + $apps = self::$DI['app']['repos.api-applications']->findByCreator(self::$DI['user']); $this->assertTrue(self::$DI['client']->getResponse()->isRedirect()); $this->assertGreaterThan($nbApp, count($apps)); @@ -121,16 +122,16 @@ class DevelopersTest extends \PhraseanetAuthenticatedWebTestCase */ public function testDeleteApp() { - $oauthApp = \API_OAuth2_Application::create(self::$DI['app'], self::$DI['user'], 'test app'); - $this->XMLHTTPRequest('DELETE', '/developers/application/' . $oauthApp->get_id() . '/'); + $oauthApp = self::$DI['app']['manipulator.api-application']->create( + 'test app', + '', + '', + 'http://phraseanet.com/' + ); + $this->XMLHTTPRequest('DELETE', '/developers/application/' . $oauthApp->getId() . '/'); $this->assertTrue(self::$DI['client']->getResponse()->isOk()); - try { - new \API_OAuth2_Application(self::$DI['app'], $oauthApp->get_id()); - $this->fail('Application not deleted'); - } catch (NotFoundHttpException $e) { - - } + $this->assertNull(self::$DI['app']['repos.api-application']->find($oauthApp->getId())); } /** @@ -183,8 +184,8 @@ class DevelopersTest extends \PhraseanetAuthenticatedWebTestCase $this->assertTrue(self::$DI['client']->getResponse()->isOk()); $content = json_decode(self::$DI['client']->getResponse()->getContent()); $this->assertTrue($content->success); - $oauthApp = new \API_OAuth2_Application(self::$DI['app'], $oauthApp->get_id()); - $this->assertEquals('my.callback.com', $oauthApp->get_redirect_uri()); + $oauthApp = self::$DI['app']['repos.api-application']->find($oauthApp->getId()); + $this->assertEquals('my.callback.com', $oauthApp->getRedirectUri()); } /** @@ -265,7 +266,7 @@ class DevelopersTest extends \PhraseanetAuthenticatedWebTestCase $this->assertTrue(self::$DI['client']->getResponse()->isOk()); $content = json_decode(self::$DI['client']->getResponse()->getContent()); $this->assertTrue($content->success); - $oauthApp = new \API_OAuth2_Application(self::$DI['app'], $oauthApp->get_id()); - $this->assertTrue($oauthApp->is_password_granted()); + $oauthApp = self::$DI['app']['repos.api-application']->find($oauthApp->getId()); + $this->assertTrue($oauthApp->isPasswordGranted()); } } diff --git a/tests/classes/PhraseanetTestCase.php b/tests/classes/PhraseanetTestCase.php index 16cfa774f3..e0b5f434ec 100644 --- a/tests/classes/PhraseanetTestCase.php +++ b/tests/classes/PhraseanetTestCase.php @@ -199,11 +199,11 @@ abstract class PhraseanetTestCase extends WebTestCase }); self::$DI['oauth2-app-user'] = self::$DI->share(function ($DI) { - return new \API_OAuth2_Application($DI['app'], self::$fixtureIds['oauth']['user']); + return new $DI['app']['repo.api-applications']->find(self::$fixtureIds['oauth']['user']); }); self::$DI['oauth2-app-user_notAdmin'] = self::$DI->share(function ($DI) { - return new \API_OAuth2_Application($DI['app'], self::$fixtureIds['oauth']['user_notAdmin']); + return new $DI['app']['repo.api-applications']->find(self::$fixtureIds['oauth']['user-not-admin']); }); self::$DI['logger'] = self::$DI->share(function () { diff --git a/tests/classes/api/oauthv2/AccountTest.php b/tests/classes/api/oauthv2/AccountTest.php index d67a2371b9..096f468cb2 100644 --- a/tests/classes/api/oauthv2/AccountTest.php +++ b/tests/classes/api/oauthv2/AccountTest.php @@ -32,7 +32,7 @@ class api_oauthv2_AccountTest extends \PhraseanetTestCase $this->assertInstanceOf('API_OAuth2_Token', $this->object->get_token()); - $this->assertInstanceOf('API_OAuth2_Application', $this->object->get_application()); + $this->assertInstanceOf('ApiApplication', $this->object->get_application()); $this->assertEquals(self::$DI['oauth2-app-user'], $this->object->get_application()); } diff --git a/tests/classes/api/oauthv2/ApplicationTest.php b/tests/classes/api/oauthv2/ApplicationTest.php index 3d499db903..575e3997a6 100644 --- a/tests/classes/api/oauthv2/ApplicationTest.php +++ b/tests/classes/api/oauthv2/ApplicationTest.php @@ -1,113 +1,104 @@ get_client_id(); - $loaded = API_OAuth2_Application::load_from_client_id(self::$DI['app'], $client_id); - $this->assertInstanceOf('API_OAuth2_Application', $loaded); + $loaded = self::$DI['app']['repo.api-applications']->findByClientId(self::$DI['oauth2-app-user']->getClientId()); + $this->assertInstanceOf('ApiApplication', $loaded); $this->assertEquals(self::$DI['oauth2-app-user'], $loaded); } public function testLoad_dev_app_by_user() { - $apps = API_OAuth2_Application::load_dev_app_by_user(self::$DI['app'], self::$DI['user']); + $apps = self::$DI['app']['repo.api-applications']->findByCreator(self::$DI['user']); $this->assertTrue(is_array($apps)); $this->assertTrue(count($apps) > 0); $found = false; foreach ($apps as $app) { - if ($app->get_id() === self::$DI['oauth2-app-user']->get_id()) + if ($app->get_id() === self::$DI['oauth2-app-user']->getId()) { $found = true; - $this->assertInstanceOf('API_OAuth2_Application', $app); + } + $this->assertInstanceOf('ApiApplication', $app); } - if ( ! $found) + if (!$found) { $this->fail(); + } } public function testLoad_app_by_user() { - $apps = API_OAuth2_Application::load_app_by_user(self::$DI['app'], self::$DI['user']); + $apps = self::$DI['app']['repo.api-applications']->findByUser(self::$DI['user']); $this->assertTrue(is_array($apps)); $this->assertTrue(count($apps) > 0); $found = false; foreach ($apps as $app) { - if ($app->get_id() === self::$DI['oauth2-app-user']->get_id()) + if ($app->get_id() === self::$DI['oauth2-app-user']->get_id()) { $found = true; - $this->assertInstanceOf('API_OAuth2_Application', $app); + } + $this->assertInstanceOf('ApiApplication', $app); } - if ( ! $found) + if (!$found) { $this->fail(); + } } public function testGettersAndSetters() { - $this->assertTrue(is_int(self::$DI['oauth2-app-user']->get_id())); - $this->assertInstanceOf('Alchemy\Phrasea\Model\Entities\User', self::$DI['oauth2-app-user']->get_creator()); - $this->assertEquals(self::$DI['user']->getId(), self::$DI['oauth2-app-user']->get_creator()->getId()); + $this->assertTrue(is_int(self::$DI['oauth2-app-user']->getId())); + $this->assertInstanceOf('Alchemy\Phrasea\Model\Entities\User', self::$DI['oauth2-app-user']->getCreator()); + $this->assertEquals(self::$DI['user']->getId(), self::$DI['oauth2-app-user']->getCreator()->getId()); + $this->assertTrue(in_array(self::$DI['oauth2-app-user']->getType(), [ApiApplication::DESKTOP_TYPE, ApiApplication::WEB_TYPE])); + $this->assertTrue(is_string(self::$DI['oauth2-app-user']->getNonce())); + $this->assertEquals(64, strlen(self::$DI['oauth2-app-user']->getNonce())); + self::$DI['oauth2-app-user']->set_type(ApiApplication::WEB_TYPE); + $this->assertEquals(ApiApplication::WEB_TYPE, self::$DI['oauth2-app-user']->getType()); + self::$DI['oauth2-app-user']->set_type(ApiApplication::DESKTOP_TYPE); + $this->assertEquals(ApiApplication::DESKTOP_TYPE, self::$DI['oauth2-app-user']->getType()); + $this->assertEquals(ApiApplication::NATIVE_APP_REDIRECT_URI, self::$DI['oauth2-app-user']->getRedirectUri()); + self::$DI['oauth2-app-user']->setType(ApiApplication::WEB_TYPE); - $this->assertTrue(in_array(self::$DI['oauth2-app-user']->get_type(), [API_OAuth2_Application::DESKTOP_TYPE, API_OAuth2_Application::WEB_TYPE])); - - $this->assertTrue(is_string(self::$DI['oauth2-app-user']->get_nonce())); - $this->assertEquals(64, strlen(self::$DI['oauth2-app-user']->get_nonce())); - - try { - self::$DI['oauth2-app-user']->set_type('prout'); - $this->fail(); - } catch (Exception_InvalidArgument $e) { - - } - - self::$DI['oauth2-app-user']->set_type(API_OAuth2_Application::WEB_TYPE); - $this->assertEquals(API_OAuth2_Application::WEB_TYPE, self::$DI['oauth2-app-user']->get_type()); - self::$DI['oauth2-app-user']->set_type(API_OAuth2_Application::DESKTOP_TYPE); - $this->assertEquals(API_OAuth2_Application::DESKTOP_TYPE, self::$DI['oauth2-app-user']->get_type()); - $this->assertEquals(API_OAuth2_Application::NATIVE_APP_REDIRECT_URI, self::$DI['oauth2-app-user']->get_redirect_uri()); - self::$DI['oauth2-app-user']->set_type(API_OAuth2_Application::WEB_TYPE); - - self::$DI['oauth2-app-user']->set_name('prout'); - $this->assertEquals('prout', self::$DI['oauth2-app-user']->get_name()); - self::$DI['oauth2-app-user']->set_name('test application for user'); - $this->assertEquals('test application for user', self::$DI['oauth2-app-user']->get_name()); + self::$DI['oauth2-app-user']->setName('prout'); + $this->assertEquals('prout', self::$DI['oauth2-app-user']->getName()); + self::$DI['oauth2-app-user']->setName('test application for user'); + $this->assertEquals('test application for user', self::$DI['oauth2-app-user']->getName()); $desc = 'prouti prouto prout prout'; - self::$DI['oauth2-app-user']->set_description($desc); - $this->assertEquals($desc, self::$DI['oauth2-app-user']->get_description()); - self::$DI['oauth2-app-user']->set_description(''); - $this->assertEquals('', self::$DI['oauth2-app-user']->get_description()); + self::$DI['oauth2-app-user']->setDescription($desc); + $this->assertEquals($desc, self::$DI['oauth2-app-user']->getDescription()); + self::$DI['oauth2-app-user']->setDescription(''); + $this->assertEquals('', self::$DI['oauth2-app-user']->getDescription()); $site = 'http://www.example.com/'; - self::$DI['oauth2-app-user']->set_website($site); - $this->assertEquals($site, self::$DI['oauth2-app-user']->get_website()); - self::$DI['oauth2-app-user']->set_website(''); - $this->assertEquals('', self::$DI['oauth2-app-user']->get_website()); + self::$DI['oauth2-app-user']->setWebsite($site); + $this->assertEquals($site, self::$DI['oauth2-app-user']->getWebsite()); + self::$DI['oauth2-app-user']->setWebsite(''); + $this->assertEquals('', self::$DI['oauth2-app-user']->getWebsite()); - $this->assertInstanceOf('DateTime', self::$DI['oauth2-app-user']->get_created_on()); + $this->assertInstanceOf('DateTime', self::$DI['oauth2-app-user']->getCreated()); + $this->assertInstanceOf('DateTime', self::$DI['oauth2-app-user']->getUpdated()); - $this->assertInstanceOf('DateTime', self::$DI['oauth2-app-user']->get_last_modified()); - - $this->assertMd5(self::$DI['oauth2-app-user']->get_client_id()); + $this->assertMd5(self::$DI['oauth2-app-user']->getClientId()); $client_id = md5('prouto'); - self::$DI['oauth2-app-user']->set_client_id($client_id); - $this->assertEquals($client_id, self::$DI['oauth2-app-user']->get_client_id()); - $this->assertMd5(self::$DI['oauth2-app-user']->get_client_id()); + self::$DI['oauth2-app-user']->seClientId($client_id); + $this->assertEquals($client_id, self::$DI['oauth2-app-user']->getClientId()); + $this->assertMd5(self::$DI['oauth2-app-user']->getClientId()); - $this->assertMd5(self::$DI['oauth2-app-user']->get_client_secret()); + $this->assertMd5(self::$DI['oauth2-app-user']->getClientSecret()); $client_secret = md5('prouto'); - self::$DI['oauth2-app-user']->set_client_secret($client_secret); - $this->assertEquals($client_secret, self::$DI['oauth2-app-user']->get_client_secret()); - $this->assertMd5(self::$DI['oauth2-app-user']->get_client_secret()); + self::$DI['oauth2-app-user']->setClientSecret($client_secret); + $this->assertEquals($client_secret, self::$DI['oauth2-app-user']->getClientSecret()); + $this->assertMd5(self::$DI['oauth2-app-user']->getClientSecret()); $uri = 'http://www.example.com/callback/'; - self::$DI['oauth2-app-user']->set_redirect_uri($uri); - $this->assertEquals($uri, self::$DI['oauth2-app-user']->get_redirect_uri()); - - $this->assertInstanceOf('API_OAuth2_Account', self::$DI['oauth2-app-user']->get_user_account(self::$DI['user'])); + self::$DI['oauth2-app-user']->setRedirectUri($uri); + $this->assertEquals($uri, self::$DI['oauth2-app-user']->getRedirectUri()); } private function assertmd5($md5) From 5873b2c2618314675ae8ca683e76eb9780b7e316 Mon Sep 17 00:00:00 2001 From: Nicolas Le Goff Date: Wed, 5 Mar 2014 20:50:53 +0100 Subject: [PATCH 043/112] Delete API_OAuth2_Application class --- lib/classes/API/OAuth2/Application.php | 718 ------------------------- 1 file changed, 718 deletions(-) delete mode 100644 lib/classes/API/OAuth2/Application.php diff --git a/lib/classes/API/OAuth2/Application.php b/lib/classes/API/OAuth2/Application.php deleted file mode 100644 index bb8dbc7cab..0000000000 --- a/lib/classes/API/OAuth2/Application.php +++ /dev/null @@ -1,718 +0,0 @@ -app = $app; - $this->id = (int) $application_id; - - $sql = ' - SELECT - application_id, creator, type, name, description, website - , created_on, last_modified, client_id, client_secret, nonce - , redirect_uri, activated, grant_password - FROM api_applications - WHERE application_id = :application_id'; - - $stmt = $this->app['phraseanet.appbox']->get_connection()->prepare($sql); - $stmt->execute([':application_id' => $this->id]); - - if (0 === $stmt->rowCount()) { - throw new NotFoundHttpException(sprintf('Application with id %d not found', $this->id)); - } - - $row = $stmt->fetch(PDO::FETCH_ASSOC); - $stmt->closeCursor(); - $this->creator = ! $row['creator'] ? null : $this->app['repo.users']->find($row['creator']); - $this->type = $row['type']; - $this->name = $row['name']; - $this->description = $row['description']; - $this->website = $row['website']; - $this->created_on = new DateTime($row['created_on']); - $this->last_modified = new DateTime($row['last_modified']); - $this->client_id = $row['client_id']; - $this->client_secret = $row['client_secret']; - $this->redirect_uri = $row['redirect_uri']; - $this->nonce = $row['nonce']; - $this->activated = ! ! $row['activated']; - $this->grant_password = ! ! $row['grant_password']; - - return $this; - } - - /** - * - * @return int - */ - public function get_id() - { - return $this->id; - } - - /** - * - * @return User - */ - public function get_creator() - { - return $this->creator; - } - - /** - * - * @return string - */ - public function get_type() - { - return $this->type; - } - - /** - * - * @return string - */ - public function get_nonce() - { - return $this->nonce; - } - - /** - * - * @param string $type - * @return API_OAuth2_Application - */ - public function set_type($type) - { - if ( ! in_array($type, [self::DESKTOP_TYPE, self::WEB_TYPE])) - throw new Exception_InvalidArgument(); - - $this->type = $type; - - if ($this->type == self::DESKTOP_TYPE) - $this->set_redirect_uri(self::NATIVE_APP_REDIRECT_URI); - - $sql = 'UPDATE api_applications SET type = :type, last_modified = NOW() - WHERE application_id = :application_id'; - - $params = [ - ':type' => $this->type - , ':application_id' => $this->id - ]; - - $stmt = $this->app['phraseanet.appbox']->get_connection()->prepare($sql); - $stmt->execute($params); - $stmt->closeCursor(); - - return $this; - } - - /** - * - * @return string - */ - public function get_name() - { - return $this->name; - } - - /** - * - * @param string $name - * @return API_OAuth2_Application - */ - public function set_name($name) - { - $this->name = $name; - - $sql = 'UPDATE api_applications SET name = :name, last_modified = NOW() - WHERE application_id = :application_id'; - - $params = [ - ':name' => $this->name - , ':application_id' => $this->id - ]; - - $stmt = $this->app['phraseanet.appbox']->get_connection()->prepare($sql); - $stmt->execute($params); - $stmt->closeCursor(); - - return $this; - } - - /** - * - * @return string - */ - public function get_description() - { - return $this->description; - } - - /** - * - * @param string $description - * @return API_OAuth2_Application - */ - public function set_description($description) - { - $this->description = $description; - - $sql = 'UPDATE api_applications - SET description = :description, last_modified = NOW() - WHERE application_id = :application_id'; - - $params = [ - ':description' => $this->description - , ':application_id' => $this->id - ]; - - $stmt = $this->app['phraseanet.appbox']->get_connection()->prepare($sql); - $stmt->execute($params); - $stmt->closeCursor(); - - return $this; - } - - /** - * - * @return string - */ - public function get_website() - { - return $this->website; - } - - /** - * - * @param string $website - * @return API_OAuth2_Application - */ - public function set_website($website) - { - $this->website = $website; - - $sql = 'UPDATE api_applications - SET website = :website, last_modified = NOW() - WHERE application_id = :application_id'; - - $params = [ - ':website' => $this->website - , ':application_id' => $this->id - ]; - - $stmt = $this->app['phraseanet.appbox']->get_connection()->prepare($sql); - $stmt->execute($params); - $stmt->closeCursor(); - - return $this; - } - - /** - * Tell wether application is activated - * @return boolean - */ - public function is_activated() - { - return $this->activated; - } - - /** - * - * @param boolean $activated - * @return API_OAuth2_Application - */ - public function set_activated($activated) - { - $this->activated = $activated; - - $sql = 'UPDATE api_applications - SET activated = :activated, last_modified = NOW() - WHERE application_id = :application_id'; - - $params = [ - ':activated' => $this->activated - , ':application_id' => $this->id - ]; - - $stmt = $this->app['phraseanet.appbox']->get_connection()->prepare($sql); - $stmt->execute($params); - $stmt->closeCursor(); - - return $this; - } - - /** - * Tell wether application authorize password grant type - * @return boolean - */ - public function is_password_granted() - { - return $this->grant_password; - } - - /** - * - * @param boolean $grant - * @return API_OAuth2_Application - */ - public function set_grant_password($grant) - { - $this->grant_password = ! ! $grant; - - $sql = 'UPDATE api_applications - SET grant_password = :grant_password, last_modified = NOW() - WHERE application_id = :application_id'; - - $params = [ - ':grant_password' => $this->grant_password - , ':application_id' => $this->id - ]; - - $stmt = $this->app['phraseanet.appbox']->get_connection()->prepare($sql); - $stmt->execute($params); - $stmt->closeCursor(); - - return $this; - } - - /** - * - * @return DateTime - */ - public function get_created_on() - { - return $this->created_on; - } - - /** - * - * @return DateTime - */ - public function get_last_modified() - { - return $this->last_modified; - } - - /** - * - * @return int - */ - public function get_client_id() - { - return $this->client_id; - } - - /** - * - * @param int $client_id - * @return API_OAuth2_Application - */ - public function set_client_id($client_id) - { - $this->client_id = $client_id; - - $sql = 'UPDATE api_applications - SET client_id = :client_id, last_modified = NOW() - WHERE application_id = :application_id'; - - $params = [ - ':client_id' => $this->client_id - , ':application_id' => $this->id - ]; - - $stmt = $this->app['phraseanet.appbox']->get_connection()->prepare($sql); - $stmt->execute($params); - $stmt->closeCursor(); - - return $this; - } - - /** - * - * @return string - */ - public function get_client_secret() - { - return $this->client_secret; - } - - /** - * - * @param string $client_secret - * @return API_OAuth2_Application - */ - public function set_client_secret($client_secret) - { - $this->client_secret = $client_secret; - - $sql = 'UPDATE api_applications - SET client_secret = :client_secret, last_modified = NOW() - WHERE application_id = :application_id'; - - $params = [ - ':client_secret' => $this->client_secret - , ':application_id' => $this->id - ]; - - $stmt = $this->app['phraseanet.appbox']->get_connection()->prepare($sql); - $stmt->execute($params); - $stmt->closeCursor(); - - return $this; - } - - /** - * - * @return string - */ - public function get_redirect_uri() - { - return $this->redirect_uri; - } - - /** - * - * @param string $redirect_uri - * @return API_OAuth2_Application - */ - public function set_redirect_uri($redirect_uri) - { - $this->redirect_uri = $redirect_uri; - $sql = 'UPDATE api_applications - SET redirect_uri = :redirect_uri, last_modified = NOW() - WHERE application_id = :application_id'; - - $params = [ - ':redirect_uri' => $this->redirect_uri - , ':application_id' => $this->id - ]; - - $stmt = $this->app['phraseanet.appbox']->get_connection()->prepare($sql); - $stmt->execute($params); - $stmt->closeCursor(); - - return $this; - } - - /** - * - * @param User $user - * @return API_OAuth2_Account - */ - public function get_user_account(User $user) - { - $sql = 'SELECT api_account_id FROM api_accounts - WHERE usr_id = :usr_id AND application_id = :id'; - - $params = [ - ':usr_id' => $user->getId() - , ':id' => $this->id - ]; - - $stmt = $this->app['phraseanet.appbox']->get_connection()->prepare($sql); - $stmt->execute($params); - $row = $stmt->fetch(PDO::FETCH_ASSOC); - $stmt->closeCursor(); - - if ( ! $row) - throw new NotFoundHttpException('Application not found.'); - - return new API_OAuth2_Account($this->app, $row['api_account_id']); - } - - /** - * - * @return void - */ - public function delete() - { - foreach ($this->get_related_accounts() as $account) { - $account->delete(); - } - - $sql = 'DELETE FROM api_applications - WHERE application_id = :application_id'; - - $stmt = $this->app['phraseanet.appbox']->get_connection()->prepare($sql); - $stmt->execute([':application_id' => $this->get_id()]); - $stmt->closeCursor(); - - return; - } - - /** - * - * @return array - */ - protected function get_related_accounts() - { - $sql = 'SELECT api_account_id FROM api_accounts - WHERE application_id = :application_id'; - - $stmt = $this->app['phraseanet.appbox']->get_connection()->prepare($sql); - $stmt->execute([':application_id' => $this->get_id()]); - $rs = $stmt->fetchAll(PDO::FETCH_ASSOC); - $stmt->closeCursor(); - - $accounts = []; - - foreach ($rs as $row) { - $accounts[] = new API_OAuth2_Account($this->app, $row['api_account_id']); - } - - return $accounts; - } - - /** - * - * @param Application $app - * @param User $user - * @param type $name - * @return API_OAuth2_Application - */ - public static function create(Application $app, User $user = null, $name) - { - $sql = ' - INSERT INTO api_applications ( - application_id, creator, created_on, name, last_modified, - nonce, client_id, client_secret, activated, grant_password - ) - VALUES ( - null, :usr_id, NOW(), :name, NOW(), :nonce, :client_id, - :client_secret, :activated, :grant_password - )'; - - $nonce = $app['random.medium']->generateString(64); - $client_secret = $app['random.medium']->generateString(32, TokenManipulator::LETTERS_AND_NUMBERS); - $client_token = $app['random.medium']->generateString(32, TokenManipulator::LETTERS_AND_NUMBERS); - - $params = [ - ':usr_id' => $user ? $user->getId() : null, - ':name' => $name, - ':client_id' => $client_token, - ':client_secret' => $client_secret, - ':nonce' => $nonce, - ':activated' => 1, - ':grant_password' => 0 - ]; - - $stmt = $app['phraseanet.appbox']->get_connection()->prepare($sql); - $stmt->execute($params); - $stmt->closeCursor(); - - $application_id = $app['phraseanet.appbox']->get_connection()->lastInsertId(); - - $application = new self($app, $application_id); - - if ($user) { - API_OAuth2_Account::create($app, $user, $application); - } - - return $application; - } - - /** - * - * @param Application $app - * @param type $client_id - * @return API_OAuth2_Application - */ - public static function load_from_client_id(Application $app, $client_id) - { - $sql = 'SELECT application_id FROM api_applications - WHERE client_id = :client_id'; - - $stmt = $app['phraseanet.appbox']->get_connection()->prepare($sql); - $stmt->execute([':client_id' => $client_id]); - $row = $stmt->fetch(PDO::FETCH_ASSOC); - $stmt->closeCursor(); - - if ( ! $row) - throw new NotFoundHttpException('Client not found.'); - - return new self($app, $row['application_id']); - } - - public static function load_dev_app_by_user(Application $app, User $user) - { - $sql = 'SELECT a.application_id - FROM api_applications a, api_accounts b - WHERE a.creator = :usr_id AND a.application_id = b.application_id'; - - $stmt = $app['phraseanet.appbox']->get_connection()->prepare($sql); - $stmt->execute([':usr_id' => $user->getId()]); - $rs = $stmt->fetchAll(PDO::FETCH_ASSOC); - $stmt->closeCursor(); - - $apps = []; - foreach ($rs as $row) { - $apps[] = new API_OAuth2_Application($app, $row['application_id']); - } - - return $apps; - } - - public static function load_app_by_user(Application $app, User $user) - { - $sql = 'SELECT a.application_id - FROM api_accounts a, api_applications c - WHERE usr_id = :usr_id AND c.application_id = a.application_id'; - - $stmt = $app['phraseanet.appbox']->get_connection()->prepare($sql); - $stmt->execute([':usr_id' => $user->getId()]); - $rs = $stmt->fetchAll(PDO::FETCH_ASSOC); - $stmt->closeCursor(); - - $apps = []; - foreach ($rs as $row) { - $apps[] = new API_OAuth2_Application($app, $row['application_id']); - } - - return $apps; - } - - public static function load_authorized_app_by_user(Application $app, User $user) - { - $sql = ' - SELECT a.application_id - FROM api_accounts a, api_applications c - WHERE usr_id = :usr_id AND c.application_id = a.application_id - AND revoked = 0'; - - $stmt = $app['phraseanet.appbox']->get_connection()->prepare($sql); - $stmt->execute([':usr_id' => $user->getId()]); - $rs = $stmt->fetchAll(PDO::FETCH_ASSOC); - $stmt->closeCursor(); - - $apps = []; - foreach ($rs as $row) { - $apps[] = new API_OAuth2_Application($app, $row['application_id']); - } - - return $apps; - } -} From 5d6ee3718a1659021d78a6c73b6f06b8273219e5 Mon Sep 17 00:00:00 2001 From: Nicolas Le Goff Date: Wed, 5 Mar 2014 20:54:38 +0100 Subject: [PATCH 044/112] Fix typo --- .../Tests/Phrasea/Controller/Api/OAuth2Test.php | 2 +- .../Tests/Phrasea/Controller/Root/DevelopersTest.php | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/Alchemy/Tests/Phrasea/Controller/Api/OAuth2Test.php b/tests/Alchemy/Tests/Phrasea/Controller/Api/OAuth2Test.php index cc8465a7e3..40cea817ca 100644 --- a/tests/Alchemy/Tests/Phrasea/Controller/Api/OAuth2Test.php +++ b/tests/Alchemy/Tests/Phrasea/Controller/Api/OAuth2Test.php @@ -120,7 +120,7 @@ class OAuth2Test extends \PhraseanetAuthenticatedWebTestCase public function testAuthorizeRedirect() { //session off - $apps = self::$DI['app']['repos.api-application']->findAuthorizedAppsByUser(self::$DI['user']); + $apps = self::$DI['app']['repo.api-application']->findAuthorizedAppsByUser(self::$DI['user']); foreach ($apps as $app) { if ($app->get_client_id() === self::$DI['oauth2-app-user']->getClientId()) { self::$DI['client']->followRedirects(); diff --git a/tests/Alchemy/Tests/Phrasea/Controller/Root/DevelopersTest.php b/tests/Alchemy/Tests/Phrasea/Controller/Root/DevelopersTest.php index 942af2e579..d65a1bfcd0 100644 --- a/tests/Alchemy/Tests/Phrasea/Controller/Root/DevelopersTest.php +++ b/tests/Alchemy/Tests/Phrasea/Controller/Root/DevelopersTest.php @@ -56,7 +56,7 @@ class DevelopersTest extends \PhraseanetAuthenticatedWebTestCase */ public function testPostNewApp() { - $apps = self::$DI['app']['repos.api-applications']->findByCreator(self::$DI['user']); + $apps = self::$DI['app']['repo.api-applications']->findByCreator(self::$DI['user']); $nbApp = count($apps); self::$DI['client']->request('POST', '/developers/application/', [ @@ -69,7 +69,7 @@ class DevelopersTest extends \PhraseanetAuthenticatedWebTestCase 'scheme-callback' => 'http://' ]); - $apps = self::$DI['app']['repos.api-applications']->findByCreator(self::$DI['user']); + $apps = self::$DI['app']['repo.api-applications']->findByCreator(self::$DI['user']); $this->assertTrue(self::$DI['client']->getResponse()->isRedirect()); $this->assertGreaterThan($nbApp, count($apps)); @@ -131,7 +131,7 @@ class DevelopersTest extends \PhraseanetAuthenticatedWebTestCase $this->XMLHTTPRequest('DELETE', '/developers/application/' . $oauthApp->getId() . '/'); $this->assertTrue(self::$DI['client']->getResponse()->isOk()); - $this->assertNull(self::$DI['app']['repos.api-application']->find($oauthApp->getId())); + $this->assertNull(self::$DI['app']['repo.api-application']->find($oauthApp->getId())); } /** @@ -184,7 +184,7 @@ class DevelopersTest extends \PhraseanetAuthenticatedWebTestCase $this->assertTrue(self::$DI['client']->getResponse()->isOk()); $content = json_decode(self::$DI['client']->getResponse()->getContent()); $this->assertTrue($content->success); - $oauthApp = self::$DI['app']['repos.api-application']->find($oauthApp->getId()); + $oauthApp = self::$DI['app']['repo.api-application']->find($oauthApp->getId()); $this->assertEquals('my.callback.com', $oauthApp->getRedirectUri()); } @@ -266,7 +266,7 @@ class DevelopersTest extends \PhraseanetAuthenticatedWebTestCase $this->assertTrue(self::$DI['client']->getResponse()->isOk()); $content = json_decode(self::$DI['client']->getResponse()->getContent()); $this->assertTrue($content->success); - $oauthApp = self::$DI['app']['repos.api-application']->find($oauthApp->getId()); + $oauthApp = self::$DI['app']['repo.api-application']->find($oauthApp->getId()); $this->assertTrue($oauthApp->isPasswordGranted()); } } From d1d936d8b8dd8e40eb07beb392a00e35f33d0f03 Mon Sep 17 00:00:00 2001 From: Nicolas Le Goff Date: Wed, 5 Mar 2014 20:58:33 +0100 Subject: [PATCH 045/112] Update ApiApplication method call --- .../Tests/Phrasea/Controller/Api/ApiTestCase.php | 2 +- .../Tests/Phrasea/Controller/Api/OAuth2Test.php | 10 +++++----- .../Tests/Phrasea/Controller/Root/AccountTest.php | 2 +- tests/classes/PhraseanetTestCase.php | 2 +- tests/classes/api/oauthv2/ApplicationTest.php | 6 +++--- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/tests/Alchemy/Tests/Phrasea/Controller/Api/ApiTestCase.php b/tests/Alchemy/Tests/Phrasea/Controller/Api/ApiTestCase.php index 1b78483f55..917077abe6 100644 --- a/tests/Alchemy/Tests/Phrasea/Controller/Api/ApiTestCase.php +++ b/tests/Alchemy/Tests/Phrasea/Controller/Api/ApiTestCase.php @@ -64,7 +64,7 @@ abstract class ApiTestCase extends \PhraseanetWebTestCase }); if (!self::$apiInitialized) { - self::$account = \API_OAuth2_Account::load_with_user(self::$DI['app'], self::$DI['oauth2-app-user_notAdmin'], self::$DI['user_notAdmin']); + self::$account = \API_OAuth2_Account::load_with_user(self::$DI['app'], self::$DI['oauth2-app-user-not-admin'], self::$DI['user_notAdmin']); self::$account->set_revoked(false); self::$token = self::$account->get_token()->get_value(); diff --git a/tests/Alchemy/Tests/Phrasea/Controller/Api/OAuth2Test.php b/tests/Alchemy/Tests/Phrasea/Controller/Api/OAuth2Test.php index 40cea817ca..8a1376be81 100644 --- a/tests/Alchemy/Tests/Phrasea/Controller/Api/OAuth2Test.php +++ b/tests/Alchemy/Tests/Phrasea/Controller/Api/OAuth2Test.php @@ -32,8 +32,8 @@ class OAuth2Test extends \PhraseanetAuthenticatedWebTestCase $this->queryParameters = [ "response_type" => "code", - "client_id" => self::$DI['oauth2-app-user']->get_client_id(), - "redirect_uri" => self::$DI['oauth2-app-user']->get_redirect_uri(), + "client_id" => self::$DI['oauth2-app-user']->getClientId(), + "redirect_uri" => self::$DI['oauth2-app-user']->getRedirectId(), "scope" => "", "state" => "valueTest" ]; @@ -96,7 +96,7 @@ class OAuth2Test extends \PhraseanetAuthenticatedWebTestCase public static function getAccount() { $sql = "SELECT api_account_id FROM api_accounts WHERE application_id = :app_id AND usr_id = :usr_id"; - $t = [":app_id" => self::$DI['oauth2-app-user']->get_id(), ":usr_id" => self::$DI['user']->getId()]; + $t = [":app_id" => self::$DI['oauth2-app-user']->getId(), ":usr_id" => self::$DI['user']->getId()]; $conn = self::$DI['app']['phraseanet.appbox']->get_connection(); $stmt = $conn->prepare($sql); $stmt->execute($t); @@ -135,8 +135,8 @@ class OAuth2Test extends \PhraseanetAuthenticatedWebTestCase self::$DI['client']->request('GET', '/api/oauthv2/authorize', $this->queryParameters); $this->assertTrue(self::$DI['client']->getResponse()->isSuccessful()); - $this->assertRegExp("/" . self::$DI['oauth2-app-user']->get_client_id() . "/", self::$DI['client']->getResponse()->getContent()); - $this->assertRegExp("/" . str_replace("/", '\/', self::$DI['oauth2-app-user']->get_redirect_uri()) . "/", self::$DI['client']->getResponse()->getContent()); + $this->assertRegExp("/" . self::$DI['oauth2-app-user']->getCLientId() . "/", self::$DI['client']->getResponse()->getContent()); + $this->assertRegExp("/" . str_replace("/", '\/', self::$DI['oauth2-app-user']->getRedirectUri()) . "/", self::$DI['client']->getResponse()->getContent()); $this->assertRegExp("/" . $this->queryParameters["response_type"] . "/", self::$DI['client']->getResponse()->getContent()); $this->assertRegExp("/" . $this->queryParameters["scope"] . "/", self::$DI['client']->getResponse()->getContent()); $this->assertRegExp("/" . $this->queryParameters["state"] . "/", self::$DI['client']->getResponse()->getContent()); diff --git a/tests/Alchemy/Tests/Phrasea/Controller/Root/AccountTest.php b/tests/Alchemy/Tests/Phrasea/Controller/Root/AccountTest.php index 7333bd9b47..1f7eb48c4c 100644 --- a/tests/Alchemy/Tests/Phrasea/Controller/Root/AccountTest.php +++ b/tests/Alchemy/Tests/Phrasea/Controller/Root/AccountTest.php @@ -376,7 +376,7 @@ class AccountTest extends \PhraseanetAuthenticatedWebTestCase */ public function testAUthorizedAppGrantAccessSuccessfull($revoke, $expected) { - self::$DI['client']->request('GET', '/account/security/application/' . self::$DI['oauth2-app-user']->get_id() . '/grant/', [ + self::$DI['client']->request('GET', '/account/security/application/' . self::$DI['oauth2-app-user']->getId() . '/grant/', [ 'revoke' => $revoke ], [], [ 'HTTP_ACCEPT' => 'application/json', diff --git a/tests/classes/PhraseanetTestCase.php b/tests/classes/PhraseanetTestCase.php index e0b5f434ec..ac85c41c5e 100644 --- a/tests/classes/PhraseanetTestCase.php +++ b/tests/classes/PhraseanetTestCase.php @@ -202,7 +202,7 @@ abstract class PhraseanetTestCase extends WebTestCase return new $DI['app']['repo.api-applications']->find(self::$fixtureIds['oauth']['user']); }); - self::$DI['oauth2-app-user_notAdmin'] = self::$DI->share(function ($DI) { + self::$DI['oauth2-app-user-not-admin'] = self::$DI->share(function ($DI) { return new $DI['app']['repo.api-applications']->find(self::$fixtureIds['oauth']['user-not-admin']); }); diff --git a/tests/classes/api/oauthv2/ApplicationTest.php b/tests/classes/api/oauthv2/ApplicationTest.php index 575e3997a6..9238f9ebe8 100644 --- a/tests/classes/api/oauthv2/ApplicationTest.php +++ b/tests/classes/api/oauthv2/ApplicationTest.php @@ -36,7 +36,7 @@ class api_oauthv2_ApplicationTest extends \PhraseanetTestCase $found = false; foreach ($apps as $app) { - if ($app->get_id() === self::$DI['oauth2-app-user']->get_id()) { + if ($app->get_id() === self::$DI['oauth2-app-user']->getId()) { $found = true; } $this->assertInstanceOf('ApiApplication', $app); @@ -55,9 +55,9 @@ class api_oauthv2_ApplicationTest extends \PhraseanetTestCase $this->assertTrue(in_array(self::$DI['oauth2-app-user']->getType(), [ApiApplication::DESKTOP_TYPE, ApiApplication::WEB_TYPE])); $this->assertTrue(is_string(self::$DI['oauth2-app-user']->getNonce())); $this->assertEquals(64, strlen(self::$DI['oauth2-app-user']->getNonce())); - self::$DI['oauth2-app-user']->set_type(ApiApplication::WEB_TYPE); + self::$DI['oauth2-app-user']->setType(ApiApplication::WEB_TYPE); $this->assertEquals(ApiApplication::WEB_TYPE, self::$DI['oauth2-app-user']->getType()); - self::$DI['oauth2-app-user']->set_type(ApiApplication::DESKTOP_TYPE); + self::$DI['oauth2-app-user']->setType(ApiApplication::DESKTOP_TYPE); $this->assertEquals(ApiApplication::DESKTOP_TYPE, self::$DI['oauth2-app-user']->getType()); $this->assertEquals(ApiApplication::NATIVE_APP_REDIRECT_URI, self::$DI['oauth2-app-user']->getRedirectUri()); self::$DI['oauth2-app-user']->setType(ApiApplication::WEB_TYPE); From 36cabf2d6a46a62992ac40b44fe70acd79af556c Mon Sep 17 00:00:00 2001 From: Nicolas Le Goff Date: Thu, 6 Mar 2014 02:05:42 +0100 Subject: [PATCH 046/112] Delete Alchemy\Phrasea\Controller\Api\Logger class --- lib/Alchemy/Phrasea/Controller/Api/Logger.php | 393 ------------------ 1 file changed, 393 deletions(-) delete mode 100644 lib/Alchemy/Phrasea/Controller/Api/Logger.php diff --git a/lib/Alchemy/Phrasea/Controller/Api/Logger.php b/lib/Alchemy/Phrasea/Controller/Api/Logger.php deleted file mode 100644 index 6370446fde..0000000000 --- a/lib/Alchemy/Phrasea/Controller/Api/Logger.php +++ /dev/null @@ -1,393 +0,0 @@ -app = $app; - $this->id = (int) $log_id; - - $sql = ' - SELECT - api_log_id, - api_account_id, - api_log_route, - api_log_date, - api_log_status_code, - api_log_format, - api_log_resource, - api_log_general, - api_log_aspect, - api_log_action - FROM - api_logs - WHERE - api_log_id = :log_id'; - - $stmt = $this->app['phraseanet.appbox']->get_connection()->prepare($sql); - $stmt->execute([':log_id' => $this->id]); - $row = $stmt->fetch(\PDO::FETCH_ASSOC); - $stmt->closeCursor(); - - $this->account_id = $row['api_account_id']; - $this->account = new \API_OAuth2_Account($this->app, (int) $row['api_account_id']); - $this->aspect = $row['api_log_aspect']; - $this->date = new \DateTime($row['api_log_date']); - $this->format = $row['api_log_format']; - $this->general = $row['api_log_general']; - $this->resource = $row['api_log_resource']; - $this->status_code = (int) $row['api_log_status_code']; - - return $this; - } - - public function get_account_id() - { - return $this->account_id; - } - - public function set_account_id($account_id) - { - $this->account_id = $account_id; - - $sql = 'UPDATE api_log - SET api_account_id = :account_id - WHERE api_log_id = :log_id'; - - $params = [ - ':api_account_id' => $this->account_id - , ':log_id' => $this->id - ]; - - $stmt = $this->app['phraseanet.appbox']->get_connection()->prepare($sql); - $stmt->execute($params); - $stmt->closeCursor(); - - return $this; - } - - public function get_date() - { - return $this->date; - } - - public function set_date(DateTime $date) - { - $this->date = $date; - - $sql = 'UPDATE api_log - SET api_log_date = :date - WHERE api_log_id = :log_id'; - - $params = [ - ':date' => $this->date->format("Y-m-d H:i:s") - , ':log_id' => $this->id - ]; - - $stmt = $this->app['phraseanet.appbox']->get_connection()->prepare($sql); - $stmt->execute($params); - $stmt->closeCursor(); - - return $this; - } - - public function get_status_code() - { - return $this->status_code; - } - - public function set_status_code($status_code) - { - $this->status_code = (int) $status_code; - - $sql = 'UPDATE api_log - SET api_log_status_code = :code - WHERE api_log_id = :log_id'; - - $params = [ - ':code' => $this->status_code - , ':log_id' => $this->id - ]; - - $stmt = $this->app['phraseanet.appbox']->get_connection()->prepare($sql); - $stmt->execute($params); - $stmt->closeCursor(); - - return $this; - } - - public function get_format() - { - return $this->format; - } - - public function set_format($format) - { - - if ( ! in_array($format, ['json', 'jsonp', 'yaml', 'unknow'])) - throw new \Exception_InvalidArgument(); - - $this->format = $format; - - $sql = 'UPDATE api_log - SET api_log_format = :format - WHERE api_log_id = :log_id'; - - $params = [ - ':format' => $this->format - , ':log_id' => $this->id - ]; - - $stmt = $this->app['phraseanet.appbox']->get_connection()->prepare($sql); - $stmt->execute($params); - $stmt->closeCursor(); - - return $this; - } - - public function get_resource() - { - return $this->resource; - } - - public function set_resource($resource) - { - if ( ! in_array($resource, [self::DATABOXES_RESOURCE, self::BASKETS_RESOURCE, self::FEEDS_RESOURCE, self::RECORDS_RESOURCE])) - throw new \Exception_InvalidArgument(); - - $this->resource = $resource; - - $sql = 'UPDATE api_log - SET api_log_resource = :resource - WHERE api_log_id = :log_id'; - - $params = [ - ':resource' => $this->resource, - ':log_id' => $this->id, - ]; - - $stmt = $this->app['phraseanet.appbox']->get_connection()->prepare($sql); - $stmt->execute($params); - $stmt->closeCursor(); - - return $this; - } - - public function get_general() - { - return $this->general; - } - - public function set_general($general) - { - $this->general = $general; - - $sql = 'UPDATE api_log - SET api_log_general = :general - WHERE api_log_id = :log_id'; - - $params = [ - ':general' => $this->general - , ':log_id' => $this->id - ]; - - $stmt = $this->app['phraseanet.appbox']->get_connection()->prepare($sql); - $stmt->execute($params); - $stmt->closeCursor(); - - return $this; - } - - public function get_aspect() - { - return $this->aspect; - } - - public function set_aspect($aspect) - { - $this->aspect = $aspect; - - $sql = 'UPDATE api_log - SET api_log_aspect = :aspect - WHERE api_log_id = :log_id'; - - $params = [ - ':aspect' => $this->aspect - , ':log_id' => $this->id - ]; - - $stmt = $this->app['phraseanet.appbox']->get_connection()->prepare($sql); - $stmt->execute($params); - $stmt->closeCursor(); - - return $this; - } - - public function get_action() - { - return $this->action; - } - - public function set_action($action) - { - $this->action = $action; - - $sql = 'UPDATE api_log - SET api_log_action = :action - WHERE api_log_id = :log_id'; - - $params = [ - ':action' => $this->action - , ':log_id' => $this->id - ]; - - $stmt = $this->app['phraseanet.appbox']->get_connection()->prepare($sql); - $stmt->execute($params); - $stmt->closeCursor(); - - return $this; - } - - public function get_account() - { - return $this->account; - } - - public static function create(Application $app, \API_OAuth2_Account $account, $route, $status_code, $format, $resource, $general = null, $aspect = null, $action = null) - { - $sql = ' - INSERT INTO - api_logs ( - api_log_id, - api_account_id, - api_log_route, - api_log_date, - api_log_status_code, - api_log_format, - api_log_resource, - api_log_general, - api_log_aspect, - api_log_action - ) - VALUES ( - null, - :account_id, - :route, - NOW(), - :status_code, - :format, - :resource, - :general, - :aspect, - :action - )'; - - $params = [ - ':account_id' => $account->get_id(), - ':route' => $route, - ':status_code' => $status_code, - ':format' => $format, - ':resource' => $resource, - ':general' => $general, - ':aspect' => $aspect, - ':action' => $action - ]; - - $stmt = $app['phraseanet.appbox']->get_connection()->prepare($sql); - $stmt->execute($params); - $stmt->closeCursor(); - - $log_id = $app['phraseanet.appbox']->get_connection()->lastInsertId(); - - return new self($app, $log_id); - } -} From fd38ec9b79d4198c129534cbba421b9062f446a7 Mon Sep 17 00:00:00 2001 From: Nicolas Le Goff Date: Thu, 6 Mar 2014 12:35:56 +0100 Subject: [PATCH 047/112] Delete references to API_OAuth2_Account class --- lib/classes/API/OAuth2/Account.php | 263 ------------------ .../web/account/authorized_apps.html.twig | 4 +- .../Phrasea/Controller/Api/ApiTestCase.php | 19 +- .../Phrasea/Controller/Api/OAuth2Test.php | 10 +- .../Phrasea/Controller/Root/AccountTest.php | 8 +- tests/classes/api/oauthv2/AccountTest.php | 31 +-- tests/classes/api/oauthv2/AuthCodeTest.php | 4 +- .../classes/api/oauthv2/RefreshTokenTest.php | 3 +- tests/classes/api/oauthv2/TokenTest.php | 4 +- 9 files changed, 34 insertions(+), 312 deletions(-) delete mode 100644 lib/classes/API/OAuth2/Account.php diff --git a/lib/classes/API/OAuth2/Account.php b/lib/classes/API/OAuth2/Account.php deleted file mode 100644 index d2d73aa6fd..0000000000 --- a/lib/classes/API/OAuth2/Account.php +++ /dev/null @@ -1,263 +0,0 @@ -app = $app; - $this->id = (int) $account_id; - $sql = 'SELECT api_account_id, usr_id, api_version, revoked - , application_id, created - FROM api_accounts - WHERE api_account_id = :api_account_id'; - - $stmt = $this->app['phraseanet.appbox']->get_connection()->prepare($sql); - $stmt->execute([':api_account_id' => $this->id]); - $row = $stmt->fetch(PDO::FETCH_ASSOC); - $stmt->closeCursor(); - - $this->application_id = (int) $row['application_id']; - $this->user = $app['repo.users']->find($row['usr_id']); - - $this->api_version = $row['api_version']; - $this->revoked = ! ! $row['revoked']; - $this->created_on = new DateTime($row['created']); - - return $this; - } - - /** - * - * @return int - */ - public function get_id() - { - return $this->id; - } - - /** - * - * @return User - */ - public function get_user() - { - return $this->user; - } - - /** - * - * @return string - */ - public function get_api_version() - { - return $this->api_version; - } - - /** - * - * @return boolean - */ - public function is_revoked() - { - return $this->revoked; - } - - /** - * - * @param boolean $boolean - * @return API_OAuth2_Account - */ - public function set_revoked($boolean) - { - $this->revoked = ! ! $boolean; - - $sql = 'UPDATE api_accounts SET revoked = :revoked - WHERE api_account_id = :account_id'; - - $params = [ - ':revoked' => ($boolean ? '1' : '0') - , 'account_id' => $this->id - ]; - - $stmt = $this->app['phraseanet.appbox']->get_connection()->prepare($sql); - $stmt->execute($params); - $stmt->closeCursor(); - - return $this; - } - - /** - * - * @return DateTime - */ - public function get_created_on() - { - return $this->created_on; - } - - /** - * - * @return API_OAuth2_Token - */ - public function get_token() - { - if (! $this->token) { - try { - $this->token = new API_OAuth2_Token($this->app['phraseanet.appbox'], $this, $this->app['random.medium']); - } catch (NotFoundHttpException $e) { - $this->token = API_OAuth2_Token::create($this->app['phraseanet.appbox'], $this, $this->app['random.medium']); - } - } - - return $this->token; - } - - /** - * - * @return API_OAuth2_Application - */ - public function get_application() - { - if ( ! $this->application) - $this->application = new API_OAuth2_Application($this->app, $this->application_id); - - return $this->application; - } - - /** - * - * @return void - */ - public function delete() - { - $this->get_token()->delete(); - - foreach (API_OAuth2_AuthCode::load_codes_by_account($this->app, $this) as $code) { - $code->delete(); - } - foreach (API_OAuth2_RefreshToken::load_by_account($this->app, $this) as $token) { - $token->delete(); - } - - $sql = 'DELETE FROM api_accounts WHERE api_account_id = :account_id'; - - $stmt = $this->app['phraseanet.appbox']->get_connection()->prepare($sql); - $stmt->execute(['account_id' => $this->id]); - $stmt->closeCursor(); - - return; - } - - public static function create(Application $app, User $user, API_OAuth2_Application $application) - { - $sql = 'INSERT INTO api_accounts - (api_account_id, usr_id, revoked, api_version, application_id, created) - VALUES (null, :usr_id, :revoked, :api_version, :application_id, :created)'; - - $datetime = new Datetime(); - $params = [ - ':usr_id' => $user->getId() - , ':application_id' => $application->get_id() - , ':api_version' => API_OAuth2_Adapter::API_VERSION - , ':revoked' => 0 - , ':created' => $datetime->format("Y-m-d H:i:s") - ]; - - $stmt = $app['phraseanet.appbox']->get_connection()->prepare($sql); - $stmt->execute($params); - $stmt->closeCursor(); - - $account_id = $app['phraseanet.appbox']->get_connection()->lastInsertId(); - - return new self($app, $account_id); - } - - public static function load_with_user(Application $app, API_OAuth2_Application $application, User $user) - { - $sql = 'SELECT api_account_id FROM api_accounts - WHERE usr_id = :usr_id AND application_id = :application_id'; - - $params = [ - ":usr_id" => $user->getId(), - ":application_id" => $application->get_id() - ]; - - $stmt = $app['phraseanet.appbox']->get_connection()->prepare($sql); - $stmt->execute($params); - $row = $stmt->fetch(PDO::FETCH_ASSOC); - $stmt->closeCursor(); - - if (! $row) { - throw new NotFoundHttpException('Account nof found.'); - } - - return new self($app, $row['api_account_id']); - } -} diff --git a/templates/web/account/authorized_apps.html.twig b/templates/web/account/authorized_apps.html.twig index 5d902759db..f54981e521 100644 --- a/templates/web/account/authorized_apps.html.twig +++ b/templates/web/account/authorized_apps.html.twig @@ -31,8 +31,8 @@ {% endif%}

- {{ "Not Allowed" | trans }} - {{ "Allowed" | trans }} + {{ "Not Allowed" | trans }} + {{ "Allowed" | trans }}

{{ application.get_description()|truncate(120, true, "...") }}

diff --git a/tests/Alchemy/Tests/Phrasea/Controller/Api/ApiTestCase.php b/tests/Alchemy/Tests/Phrasea/Controller/Api/ApiTestCase.php index 917077abe6..f6701ca56f 100644 --- a/tests/Alchemy/Tests/Phrasea/Controller/Api/ApiTestCase.php +++ b/tests/Alchemy/Tests/Phrasea/Controller/Api/ApiTestCase.php @@ -64,13 +64,13 @@ abstract class ApiTestCase extends \PhraseanetWebTestCase }); if (!self::$apiInitialized) { - self::$account = \API_OAuth2_Account::load_with_user(self::$DI['app'], self::$DI['oauth2-app-user-not-admin'], self::$DI['user_notAdmin']); - self::$account->set_revoked(false); - self::$token = self::$account->get_token()->get_value(); + self::$account = self::$DI['app']['repo.api-accounts']->findByUserAndApplication(self::$DI['user_notAdmin'], self::$DI['oauth2-app-user-not-admin']); + self::$DI['app']['manipulator.api-account']->revokeAccess(self::$account); + self::$token = self::$account->getOAuthToken()->getOauthToken(); - self::$adminAccount = \API_OAuth2_Account::load_with_user(self::$DI['app'], self::$DI['oauth2-app-user'], self::$DI['user']); - self::$adminAccount->set_revoked(false); - self::$adminToken = self::$adminAccount->get_token()->get_value(); + self::$adminAccount = self::$DI['app']['repo.api-accounts']->findByUserAndApplication(self::$DI['user'], self::$DI['oauth2-app-user']); + self::$DI['app']['manipulator.api-account']->revokeAccess(self::$adminAccount); + self::$adminAccount = self::$adminAccount->getOAuthToken()->getOauthToken(); self::$apiInitialized = true; } @@ -172,9 +172,10 @@ abstract class ApiTestCase extends \PhraseanetWebTestCase if (null === $nativeApp) { throw new \Exception(sprintf('%s not found', \API_OAuth2_Application_Navigator::CLIENT_ID)); } - $account = \API_OAuth2_Account::create(self::$DI['app'], self::$DI['user'], $nativeApp); - $token = $account->get_token()->get_value(); - $this->setToken($token); + $account = self::$DI['app']['manipulator.api-account']->create($nativeApp, self::$DI['user']); + $token = self::$DI['app']['manipulator.api-oauth-token']->create($account); + + $this->setToken($token->getOauthToken()); self::$DI['client']->request('GET', '/api/v1/databoxes/list/', $this->getParameters(), [], ['HTTP_Accept' => $this->getAcceptMimeType()]); $content = $this->unserialize(self::$DI['client']->getResponse()->getContent()); diff --git a/tests/Alchemy/Tests/Phrasea/Controller/Api/OAuth2Test.php b/tests/Alchemy/Tests/Phrasea/Controller/Api/OAuth2Test.php index 8a1376be81..dfb7d0ce76 100644 --- a/tests/Alchemy/Tests/Phrasea/Controller/Api/OAuth2Test.php +++ b/tests/Alchemy/Tests/Phrasea/Controller/Api/OAuth2Test.php @@ -95,15 +95,7 @@ class OAuth2Test extends \PhraseanetAuthenticatedWebTestCase public static function getAccount() { - $sql = "SELECT api_account_id FROM api_accounts WHERE application_id = :app_id AND usr_id = :usr_id"; - $t = [":app_id" => self::$DI['oauth2-app-user']->getId(), ":usr_id" => self::$DI['user']->getId()]; - $conn = self::$DI['app']['phraseanet.appbox']->get_connection(); - $stmt = $conn->prepare($sql); - $stmt->execute($t); - $row = $stmt->fetch(\PDO::FETCH_ASSOC); - $stmt->closeCursor(); - - return new \API_OAuth2_Account(self::$DI['app'], $row["api_account_id"]); + return self::$DI['app']['repo.api-accounts']->findByUserAndApplication(self::$DI['user'], self::$DI['oauth2-app-user']); } public function setQueryParameters($parameter, $value) diff --git a/tests/Alchemy/Tests/Phrasea/Controller/Root/AccountTest.php b/tests/Alchemy/Tests/Phrasea/Controller/Root/AccountTest.php index 1f7eb48c4c..f5d4edb5f9 100644 --- a/tests/Alchemy/Tests/Phrasea/Controller/Root/AccountTest.php +++ b/tests/Alchemy/Tests/Phrasea/Controller/Root/AccountTest.php @@ -391,13 +391,9 @@ class AccountTest extends \PhraseanetAuthenticatedWebTestCase $this->assertObjectHasAttribute('success', $json); $this->assertTrue($json->success); - $account = \API_OAuth2_Account::load_with_user( - self::$DI['app'] - , self::$DI['oauth2-app-user'] - , self::$DI['user'] - ); + $account = self::$DI['app']['repo.api-accounts']->findByUserAndApplication(self::$DI['user'], self::$DI['oauth2-app-user']); - $this->assertEquals($expected, $account->is_revoked()); + $this->assertEquals($expected, $account->isRevoked()); } public function revokeProvider() diff --git a/tests/classes/api/oauthv2/AccountTest.php b/tests/classes/api/oauthv2/AccountTest.php index 096f468cb2..e65c72e6f5 100644 --- a/tests/classes/api/oauthv2/AccountTest.php +++ b/tests/classes/api/oauthv2/AccountTest.php @@ -3,43 +3,40 @@ class api_oauthv2_AccountTest extends \PhraseanetTestCase { /** - * @var API_OAuth2_Account + * @var ApiApplication */ protected $object; public function setUp() { parent::setUp(); - $this->object = API_OAuth2_Account::load_with_user(self::$DI['app'], self::$DI['oauth2-app-user'], self::$DI['user']); + $this->object = self::$DI['app']['repo.api-accounts']->findByUserAndApplication(self::$DI['user'], self::$DI['oauth2-app-user']); } public function testGettersAndSetters() { - $this->assertTrue(is_int($this->object->get_id())); - $this->assertInstanceOf('Alchemy\Phrasea\Model\Entities\User', $this->object->get_user()); - $this->assertEquals(self::$DI['user']->getId(), $this->object->get_user()->getId()); + $this->assertTrue(is_int($this->object->getId())); + $this->assertInstanceOf('Alchemy\Phrasea\Model\Entities\User', $this->object->getUser()); + $this->assertEquals(self::$DI['user']->getId(), $this->object->getUser()->getId()); - $this->assertEquals('1.0', $this->object->get_api_version()); + $this->assertEquals('1.0', $this->object->getApiVersion()); - $this->assertTrue(is_bool($this->object->is_revoked())); + $this->assertTrue(is_bool($this->object->isRevoked())); $this->object->set_revoked(true); - $this->assertTrue($this->object->is_revoked()); + $this->assertTrue($this->object->isRevoked()); $this->object->set_revoked(false); - $this->assertFalse($this->object->is_revoked()); + $this->assertFalse($this->object->isRevoked()); - $this->assertInstanceOf('DateTime', $this->object->get_created_on()); - - $this->assertInstanceOf('API_OAuth2_Token', $this->object->get_token()); - - $this->assertInstanceOf('ApiApplication', $this->object->get_application()); - $this->assertEquals(self::$DI['oauth2-app-user'], $this->object->get_application()); + $this->assertInstanceOf('DateTime', $this->object->getCreated()); + $this->assertInstanceOf('ApiApplication', $this->object->getApplication()); + $this->assertEquals(self::$DI['oauth2-app-user'], $this->object->getApplication()); } public function testLoad_with_user() { - $loaded = API_OAuth2_Account::load_with_user(self::$DI['app'], self::$DI['oauth2-app-user'], self::$DI['user']); - $this->assertInstanceOf('API_OAuth2_Account', $loaded); + $loaded = self::$DI['app']['repo.api-accounts']->findByUserAndApplication(self::$DI['user'], self::$DI['oauth2-app-user']); + $this->assertInstanceOf('ApiAccount', $loaded); $this->assertEquals($this->object, $loaded); } } diff --git a/tests/classes/api/oauthv2/AuthCodeTest.php b/tests/classes/api/oauthv2/AuthCodeTest.php index 67ac2ae7ae..f4dc108b5b 100644 --- a/tests/classes/api/oauthv2/AuthCodeTest.php +++ b/tests/classes/api/oauthv2/AuthCodeTest.php @@ -15,7 +15,7 @@ class api_oauthv2_AuthCodeTest extends \PhraseanetTestCase public function setUp() { parent::setUp(); - $this->account = API_OAuth2_Account::load_with_user(self::$DI['app'], self::$DI['oauth2-app-user'], self::$DI['user']); + $this->account = self::$DI['app']['repo.api-accounts']->findByUserAndApplication(self::$DI['user'], self::$DI['oauth2-app-user']); $expires = time() + 100; $this->code = self::$DI['app']['random.low']->generateString(8); $this->object = API_OAuth2_AuthCode::create(self::$DI['app'], $this->account, $this->code, $expires); @@ -28,7 +28,7 @@ class api_oauthv2_AuthCodeTest extends \PhraseanetTestCase public function testGet_account() { - $this->assertInstanceOf('API_OAuth2_Account', $this->object->get_account()); + $this->assertInstanceOf('ApiApplication', $this->object->get_account()); } public function testGet_redirect_uri() diff --git a/tests/classes/api/oauthv2/RefreshTokenTest.php b/tests/classes/api/oauthv2/RefreshTokenTest.php index 9b1bb8c732..e9651461ca 100644 --- a/tests/classes/api/oauthv2/RefreshTokenTest.php +++ b/tests/classes/api/oauthv2/RefreshTokenTest.php @@ -14,8 +14,7 @@ class api_oauthv2_RefreshTokenTest extends \PhraseanetTestCase public function setUp() { parent::setUp(); - $this->account = API_OAuth2_Account::load_with_user(self::$DI['app'], self::$DI['oauth2-app-user'], self::$DI['user']); - + $this->account = self::$DI['app']['repo.api-accounts']->findByUserAndApplication(self::$DI['user'], self::$DI['oauth2-app-user']); $expires = time() + 100; $this->token = self::$DI['app']['random.low']->generateString(8); $this->scope = 'scopidou'; diff --git a/tests/classes/api/oauthv2/TokenTest.php b/tests/classes/api/oauthv2/TokenTest.php index 2e4e555b0a..5ba428ec9e 100644 --- a/tests/classes/api/oauthv2/TokenTest.php +++ b/tests/classes/api/oauthv2/TokenTest.php @@ -10,7 +10,7 @@ class api_oauthv2_TokenTest extends \PhraseanetTestCase public function setUp() { parent::setUp(); - $account = API_OAuth2_Account::load_with_user(self::$DI['app'], self::$DI['oauth2-app-user'], self::$DI['user']); + $account = self::$DI['app']['repo.api-accounts']->findByUserAndApplication(self::$DI['user'], self::$DI['oauth2-app-user']); try { new API_OAuth2_Token(self::$DI['app']['phraseanet.appbox'], $account, self::$DI['app']['random.medium']); @@ -65,7 +65,7 @@ class api_oauthv2_TokenTest extends \PhraseanetTestCase $this->object->set_scope($scope); $this->assertEquals($scope, $this->object->get_scope()); - $this->assertInstanceOf('API_OAuth2_Account', $this->object->get_account()); + $this->assertInstanceOf('ApiApplication', $this->object->get_account()); } public function testRenew() From a6db51a8068192ca6dbd6a31a6493309b36e95db Mon Sep 17 00:00:00 2001 From: Nicolas Le Goff Date: Thu, 6 Mar 2014 12:37:29 +0100 Subject: [PATCH 048/112] Delete references to API_OAuth2_Code class --- lib/classes/API/OAuth2/AuthCode.php | 180 ---------------------------- 1 file changed, 180 deletions(-) delete mode 100644 lib/classes/API/OAuth2/AuthCode.php diff --git a/lib/classes/API/OAuth2/AuthCode.php b/lib/classes/API/OAuth2/AuthCode.php deleted file mode 100644 index a9773968a5..0000000000 --- a/lib/classes/API/OAuth2/AuthCode.php +++ /dev/null @@ -1,180 +0,0 @@ -app = $app; - $this->code = $code; - $sql = 'SELECT code, api_account_id, redirect_uri, UNIX_TIMESTAMP(expires) AS expires, scope - FROM api_oauth_codes WHERE code = :code'; - - $stmt = $this->app['phraseanet.appbox']->get_connection()->prepare($sql); - $stmt->execute([':code' => $this->code]); - $row = $stmt->fetch(PDO::FETCH_ASSOC); - $stmt->closeCursor(); - - if ( ! $row) - throw new NotFoundHttpException('Code not found'); - - $this->account_id = (int) $row['api_account_id']; - $this->redirect_uri = $row['redirect_uri']; - $this->expires = $row['expires']; - $this->scope = $row['scope']; - - return $this; - } - - public function get_code() - { - return $this->code; - } - - /** - * - * @return API_OAuth2_Account - */ - public function get_account() - { - if ( ! $this->account) - $this->account = new API_OAuth2_Account($this->app, $this->account_id); - - return $this->account; - } - - public function get_redirect_uri() - { - return $this->redirect_uri; - } - - public function set_redirect_uri($redirect_uri) - { - $sql = 'UPDATE api_oauth_codes SET redirect_uri = :redirect_uri - WHERE code = :code'; - - $params = [':redirect_uri' => $redirect_uri, ':code' => $this->code]; - - $stmt = $this->app['phraseanet.appbox']->get_connection()->prepare($sql); - $stmt->execute($params); - $stmt->closeCursor(); - - $this->redirect_uri = $redirect_uri; - - return $this; - } - - /** - * - * @return int - */ - public function get_expires() - { - return $this->expires; - } - - public function get_scope() - { - return $this->scope; - } - - public function set_scope($scope) - { - $sql = 'UPDATE api_oauth_codes SET scope = :scope - WHERE code = :code'; - - $params = [':scope' => $scope, ':code' => $this->code]; - - $stmt = $this->app['phraseanet.appbox']->get_connection()->prepare($sql); - $stmt->execute($params); - $stmt->closeCursor(); - - $this->scope = $scope; - - return $this; - } - - public function delete() - { - $sql = 'DELETE FROM api_oauth_codes WHERE code = :code'; - - $stmt = $this->app['phraseanet.appbox']->get_connection()->prepare($sql); - $stmt->execute([':code' => $this->code]); - $stmt->closeCursor(); - - return; - } - - /** - * - * @param Application $app - * @param API_OAuth2_Account $account - * @return array - */ - public static function load_codes_by_account(Application $app, API_OAuth2_Account $account) - { - $sql = 'SELECT code FROM api_oauth_codes - WHERE api_account_id = :account_id'; - - $stmt = $app['phraseanet.appbox']->get_connection()->prepare($sql); - - $params = [":account_id" => $account->get_id()]; - $stmt->execute($params); - $rs = $stmt->fetchAll(PDO::FETCH_ASSOC); - $stmt->closeCursor(); - - $codes = []; - - foreach ($rs as $row) { - $codes[] = new API_OAuth2_AuthCode($app, $row['code']); - } - - return $codes; - } - - /** - * - * @param Application $app - * @param API_OAuth2_Account $account - * @param type $code - * @param int $expires - * @return API_OAuth2_AuthCode - */ - public static function create(Application $app, API_OAuth2_Account $account, $code, $expires) - { - - $sql = 'INSERT INTO api_oauth_codes (code, api_account_id, expires) - VALUES (:code, :account_id, FROM_UNIXTIME(:expires))'; - - $stmt = $app['phraseanet.appbox']->get_connection()->prepare($sql); - - $params = [ - ":code" => $code, - ":account_id" => $account->get_id(), - ":expires" => $expires - ]; - $stmt->execute($params); - $stmt->closeCursor(); - - return new self($app, $code); - } -} From c7d100ce89ce0afe67ff4762cf281785a3efaa11 Mon Sep 17 00:00:00 2001 From: Nicolas Le Goff Date: Thu, 6 Mar 2014 12:38:03 +0100 Subject: [PATCH 049/112] Delete references to API_OAuth2_RefreshToken class --- lib/classes/API/OAuth2/RefreshToken.php | 139 ------------------------ 1 file changed, 139 deletions(-) delete mode 100644 lib/classes/API/OAuth2/RefreshToken.php diff --git a/lib/classes/API/OAuth2/RefreshToken.php b/lib/classes/API/OAuth2/RefreshToken.php deleted file mode 100644 index de9bae3a31..0000000000 --- a/lib/classes/API/OAuth2/RefreshToken.php +++ /dev/null @@ -1,139 +0,0 @@ -app = $app; - $this->token = $token; - - $sql = 'SELECT api_account_id, UNIX_TIMESTAMP(expires) AS expires, scope - FROM api_oauth_refresh_tokens WHERE refresh_token = :token'; - - $stmt = $this->app['phraseanet.appbox']->get_connection()->prepare($sql); - $stmt->execute([':token' => $this->token]); - $row = $stmt->fetch(PDO::FETCH_ASSOC); - $stmt->closeCursor(); - - $this->account_id = (int) $row['api_account_id']; - $this->expires = $row['expires']; - $this->scope = $row['scope']; - - return $this; - } - - public function get_value() - { - return $this->token; - } - - /** - * - * @return API_OAuth2_Account - */ - public function get_account() - { - if (! $this->account) { - $this->account = new API_OAuth2_Account($this->app, $this->account_id); - } - - return $this->account; - } - - /** - * - * @return int - */ - public function get_expires() - { - return $this->expires; - } - - public function get_scope() - { - return $this->scope; - } - - public function delete() - { - $sql = 'DELETE FROM api_oauth_refresh_tokens - WHERE refresh_token = :refresh_token'; - - $stmt = $this->app['phraseanet.appbox']->get_connection()->prepare($sql); - $stmt->execute([":refresh_token" => $this->token]); - $stmt->closeCursor(); - - return; - } - - /** - * - * @param Application $app - * @param API_OAuth2_Account $account - * @return array - */ - public static function load_by_account(Application $app, API_OAuth2_Account $account) - { - $sql = 'SELECT refresh_token FROM api_oauth_refresh_tokens - WHERE api_account_id = :account_id'; - - $stmt = $app['phraseanet.appbox']->get_connection()->prepare($sql); - $stmt->execute([':account_id' => $account->get_id()]); - $rs = $stmt->fetchAll(PDO::FETCH_ASSOC); - $stmt->closeCursor(); - - $tokens = []; - - foreach ($rs as $row) { - $tokens[] = new API_OAuth2_RefreshToken($app, $row['refresh_token']); - } - - return $tokens; - } - - /** - * - * @param Application $app - * @param API_OAuth2_Account $account - * @param int $expires - * @param type $refresh_token - * @param type $scope - * @return API_OAuth2_RefreshToken - */ - public static function create(Application $app, API_OAuth2_Account $account, $expires, $refresh_token, $scope) - { - $sql = 'INSERT INTO api_oauth_refresh_tokens - (refresh_token, api_account_id, expires, scope) - VALUES (:refresh_token, :account_id, :expires, :scope)'; - - $stmt = $app['phraseanet.appbox']->get_connection()->prepare($sql); - $params = [ - ":refresh_token" => $refresh_token, - ":account_id" => $account->get_id(), - ":expires" => $expires, - ":scope" => $scope - ]; - $stmt->execute($params); - $stmt->closeCursor(); - - return new self($app, $refresh_token); - } -} From c107bc9cb954a434878a4d1f7760df67dee69d18 Mon Sep 17 00:00:00 2001 From: Nicolas Le Goff Date: Thu, 6 Mar 2014 12:40:00 +0100 Subject: [PATCH 050/112] Delete references to API_OAuth2_Token class --- lib/classes/API/OAuth2/Token.php | 319 ------------------ .../Phrasea/Controller/Api/ApiTestCase.php | 10 +- 2 files changed, 5 insertions(+), 324 deletions(-) delete mode 100644 lib/classes/API/OAuth2/Token.php diff --git a/lib/classes/API/OAuth2/Token.php b/lib/classes/API/OAuth2/Token.php deleted file mode 100644 index d85b5db509..0000000000 --- a/lib/classes/API/OAuth2/Token.php +++ /dev/null @@ -1,319 +0,0 @@ -appbox = $appbox; - $this->account = $account; - $this->generator = $generator; - - $sql = 'SELECT oauth_token, session_id, UNIX_TIMESTAMP(expires) as expires, scope - FROM api_oauth_tokens - WHERE api_account_id = :account_id'; - $stmt = $this->appbox->get_connection()->prepare($sql); - $stmt->execute([':account_id' => $this->account->get_id()]); - $row = $stmt->fetch(PDO::FETCH_ASSOC); - - if ( ! $row) - throw new NotFoundHttpException('Account not found'); - - $stmt->closeCursor(); - - $this->token = $row['oauth_token']; - $this->session_id = is_null($row['session_id']) ? null : (int) $row['session_id']; - $this->expires = $row['expires']; - $this->scope = $row['scope']; - - return $this; - } - - /** - * - * @return string - */ - public function get_value() - { - return $this->token; - } - - /** - * - * @param string $oauth_token - * @return API_OAuth2_Token - */ - public function set_value($oauth_token) - { - $sql = 'UPDATE api_oauth_tokens SET oauth_token = :oauth_token - WHERE oauth_token = :current_token'; - - $params = [ - ':oauth_token' => $oauth_token - , ':current_token' => $this->token - ]; - - $stmt = $this->appbox->get_connection()->prepare($sql); - $stmt->execute($params); - $stmt->closeCursor(); - - $this->token = $oauth_token; - - return $this; - } - - /** - * - * @return int - */ - public function get_session_id() - { - return $this->session_id; - } - - /** - * - * @param int $session_id - * @return API_OAuth2_Token - */ - public function set_session_id($session_id) - { - $sql = 'UPDATE api_oauth_tokens SET session_id = :session_id - WHERE oauth_token = :current_token'; - - $params = [ - ':session_id' => $session_id - , ':current_token' => $this->token - ]; - - $stmt = $this->appbox->get_connection()->prepare($sql); - $stmt->execute($params); - $stmt->closeCursor(); - - $this->session_id = $session_id !== null ? (int) $session_id : $session_id; - - return $this; - } - - /** - * - * @return int - */ - public function get_expires() - { - return $this->expires; - } - - /** - * - * @param int $expires - * @return API_OAuth2_Token - */ - public function set_expires($expires) - { - $sql = 'UPDATE api_oauth_tokens SET expires = FROM_UNIXTIME(:expires) - WHERE oauth_token = :oauth_token'; - - $params = [ - ':expires' => $expires - , ':oauth_token' => $this->get_value() - ]; - - $stmt = $this->appbox->get_connection()->prepare($sql); - $stmt->execute($params); - $stmt->closeCursor(); - - $this->expires = $expires; - - return $this; - } - - /** - * - * @return string - */ - public function get_scope() - { - return $this->scope; - } - - public function set_scope($scope) - { - $sql = 'UPDATE api_oauth_tokens SET scope = :scope - WHERE oauth_token = :oauth_token'; - - $params = [ - ':scope' => $scope - , ':oauth_token' => $this->get_value() - ]; - - $stmt = $this->appbox->get_connection()->prepare($sql); - $stmt->execute($params); - $stmt->closeCursor(); - - $this->scope = $scope; - - return $this; - } - - /** - * - * @return API_OAuth2_Account - */ - public function get_account() - { - return $this->account; - } - - /** - * - * @return API_OAuth2_Token - */ - public function renew() - { - $sql = 'UPDATE api_oauth_tokens SET oauth_token = :new_token - WHERE oauth_token = :old_token'; - - $new_token = $this->generator->generateString(32, TokenManipulator::LETTERS_AND_NUMBERS); - - $params = [ - ':new_token' => $new_token - , ':old_token' => $this->get_value() - ]; - - $stmt = $this->appbox->get_connection()->prepare($sql); - $stmt->execute($params); - $stmt->closeCursor(); - - $this->token = $new_token; - - return $this; - } - - /** - * - * @return void - */ - public function delete() - { - $sql = 'DELETE FROM api_oauth_tokens WHERE oauth_token = :oauth_token'; - - $stmt = $this->appbox->get_connection()->prepare($sql); - $stmt->execute([':oauth_token' => $this->get_value()]); - $stmt->closeCursor(); - - return; - } - - /** - * - * @param Application $app - * @param string $oauth_token - * @return API_OAuth2_Token - */ - public static function load_by_oauth_token(Application $app, $oauth_token) - { - $sql = 'SELECT a.api_account_id - FROM api_oauth_tokens a, api_accounts b - WHERE a.oauth_token = :oauth_token - AND a.api_account_id = b.api_account_id'; - - $stmt = $app['phraseanet.appbox']->get_connection()->prepare($sql); - $params = [":oauth_token" => $oauth_token]; - $stmt->execute($params); - $row = $stmt->fetch(PDO::FETCH_ASSOC); - $stmt->closeCursor(); - - if (!$row) { - throw new NotFoundHttpException('Account not found'); - } - - return new self($app['phraseanet.appbox'], new API_OAuth2_Account($app, $row['api_account_id']), $app['random.medium']); - } - - /** - * - * @param appbox $appbox - * @param API_OAuth2_Account $account - * @param string $scope - * @return API_OAuth2_Token - */ - public static function create(appbox $appbox, API_OAuth2_Account $account, Generator $generator, $scope = null) - { - $sql = 'INSERT INTO api_oauth_tokens - (oauth_token, session_id, api_account_id, expires, scope) - VALUES (:token, null, :account_id, :expire, :scope)'; - - $expires = new \DateTime('+1 hour'); - - $params = [ - ':token' => $generator->generateString(32, TokenManipulator::LETTERS_AND_NUMBERS) - , ':account_id' => $account->get_id() - , ':expire' => $expires->format(DATE_ISO8601) - , ':scope' => $scope - ]; - - $stmt = $appbox->get_connection()->prepare($sql); - $stmt->execute($params); - $stmt->closeCursor(); - - return new API_OAuth2_Token($appbox, $account, $generator); - } -} diff --git a/tests/Alchemy/Tests/Phrasea/Controller/Api/ApiTestCase.php b/tests/Alchemy/Tests/Phrasea/Controller/Api/ApiTestCase.php index f6701ca56f..dda387dacf 100644 --- a/tests/Alchemy/Tests/Phrasea/Controller/Api/ApiTestCase.php +++ b/tests/Alchemy/Tests/Phrasea/Controller/Api/ApiTestCase.php @@ -19,12 +19,12 @@ use Symfony\Component\HttpFoundation\Response; abstract class ApiTestCase extends \PhraseanetWebTestCase { /** - * @var \API_OAuth2_Token + * @var ApiOauthToken */ private static $token; /** - * @var \API_OAuth2_Account + * @var ApiAccount */ private static $account; /** @@ -32,15 +32,15 @@ abstract class ApiTestCase extends \PhraseanetWebTestCase */ private static $oauthApplication; /** - * @var \API_OAuth2_Token + * @var ApiOauthToken */ private static $adminToken; /** - * @var \API_OAuth2_Account + * @var ApiAccount */ private static $adminAccount; /** - * @var \ApiApplication + * @var ApiApplication */ private static $adminApplication; private static $apiInitialized = false; From bc4a58bf15e511cc973b3765bcc29c51dea7f377 Mon Sep 17 00:00:00 2001 From: Nicolas Le Goff Date: Thu, 6 Mar 2014 12:41:02 +0100 Subject: [PATCH 051/112] Delete api_* class tests --- tests/classes/api/oauthv2/AccountTest.php | 42 ------- tests/classes/api/oauthv2/ApplicationTest.php | 108 ------------------ tests/classes/api/oauthv2/AuthCodeTest.php | 70 ------------ .../classes/api/oauthv2/RefreshTokenTest.php | 95 --------------- tests/classes/api/oauthv2/TokenTest.php | 88 -------------- 5 files changed, 403 deletions(-) delete mode 100644 tests/classes/api/oauthv2/AccountTest.php delete mode 100644 tests/classes/api/oauthv2/ApplicationTest.php delete mode 100644 tests/classes/api/oauthv2/AuthCodeTest.php delete mode 100644 tests/classes/api/oauthv2/RefreshTokenTest.php delete mode 100644 tests/classes/api/oauthv2/TokenTest.php diff --git a/tests/classes/api/oauthv2/AccountTest.php b/tests/classes/api/oauthv2/AccountTest.php deleted file mode 100644 index e65c72e6f5..0000000000 --- a/tests/classes/api/oauthv2/AccountTest.php +++ /dev/null @@ -1,42 +0,0 @@ -object = self::$DI['app']['repo.api-accounts']->findByUserAndApplication(self::$DI['user'], self::$DI['oauth2-app-user']); - } - - public function testGettersAndSetters() - { - $this->assertTrue(is_int($this->object->getId())); - $this->assertInstanceOf('Alchemy\Phrasea\Model\Entities\User', $this->object->getUser()); - $this->assertEquals(self::$DI['user']->getId(), $this->object->getUser()->getId()); - - $this->assertEquals('1.0', $this->object->getApiVersion()); - - $this->assertTrue(is_bool($this->object->isRevoked())); - - $this->object->set_revoked(true); - $this->assertTrue($this->object->isRevoked()); - $this->object->set_revoked(false); - $this->assertFalse($this->object->isRevoked()); - - $this->assertInstanceOf('DateTime', $this->object->getCreated()); - $this->assertInstanceOf('ApiApplication', $this->object->getApplication()); - $this->assertEquals(self::$DI['oauth2-app-user'], $this->object->getApplication()); - } - - public function testLoad_with_user() - { - $loaded = self::$DI['app']['repo.api-accounts']->findByUserAndApplication(self::$DI['user'], self::$DI['oauth2-app-user']); - $this->assertInstanceOf('ApiAccount', $loaded); - $this->assertEquals($this->object, $loaded); - } -} diff --git a/tests/classes/api/oauthv2/ApplicationTest.php b/tests/classes/api/oauthv2/ApplicationTest.php deleted file mode 100644 index 9238f9ebe8..0000000000 --- a/tests/classes/api/oauthv2/ApplicationTest.php +++ /dev/null @@ -1,108 +0,0 @@ -findByClientId(self::$DI['oauth2-app-user']->getClientId()); - $this->assertInstanceOf('ApiApplication', $loaded); - $this->assertEquals(self::$DI['oauth2-app-user'], $loaded); - } - - public function testLoad_dev_app_by_user() - { - $apps = self::$DI['app']['repo.api-applications']->findByCreator(self::$DI['user']); - $this->assertTrue(is_array($apps)); - $this->assertTrue(count($apps) > 0); - $found = false; - foreach ($apps as $app) { - if ($app->get_id() === self::$DI['oauth2-app-user']->getId()) { - $found = true; - } - $this->assertInstanceOf('ApiApplication', $app); - } - - if (!$found) { - $this->fail(); - } - } - - public function testLoad_app_by_user() - { - $apps = self::$DI['app']['repo.api-applications']->findByUser(self::$DI['user']); - $this->assertTrue(is_array($apps)); - $this->assertTrue(count($apps) > 0); - $found = false; - - foreach ($apps as $app) { - if ($app->get_id() === self::$DI['oauth2-app-user']->getId()) { - $found = true; - } - $this->assertInstanceOf('ApiApplication', $app); - } - - if (!$found) { - $this->fail(); - } - } - - public function testGettersAndSetters() - { - $this->assertTrue(is_int(self::$DI['oauth2-app-user']->getId())); - $this->assertInstanceOf('Alchemy\Phrasea\Model\Entities\User', self::$DI['oauth2-app-user']->getCreator()); - $this->assertEquals(self::$DI['user']->getId(), self::$DI['oauth2-app-user']->getCreator()->getId()); - $this->assertTrue(in_array(self::$DI['oauth2-app-user']->getType(), [ApiApplication::DESKTOP_TYPE, ApiApplication::WEB_TYPE])); - $this->assertTrue(is_string(self::$DI['oauth2-app-user']->getNonce())); - $this->assertEquals(64, strlen(self::$DI['oauth2-app-user']->getNonce())); - self::$DI['oauth2-app-user']->setType(ApiApplication::WEB_TYPE); - $this->assertEquals(ApiApplication::WEB_TYPE, self::$DI['oauth2-app-user']->getType()); - self::$DI['oauth2-app-user']->setType(ApiApplication::DESKTOP_TYPE); - $this->assertEquals(ApiApplication::DESKTOP_TYPE, self::$DI['oauth2-app-user']->getType()); - $this->assertEquals(ApiApplication::NATIVE_APP_REDIRECT_URI, self::$DI['oauth2-app-user']->getRedirectUri()); - self::$DI['oauth2-app-user']->setType(ApiApplication::WEB_TYPE); - - self::$DI['oauth2-app-user']->setName('prout'); - $this->assertEquals('prout', self::$DI['oauth2-app-user']->getName()); - self::$DI['oauth2-app-user']->setName('test application for user'); - $this->assertEquals('test application for user', self::$DI['oauth2-app-user']->getName()); - - $desc = 'prouti prouto prout prout'; - self::$DI['oauth2-app-user']->setDescription($desc); - $this->assertEquals($desc, self::$DI['oauth2-app-user']->getDescription()); - self::$DI['oauth2-app-user']->setDescription(''); - $this->assertEquals('', self::$DI['oauth2-app-user']->getDescription()); - - $site = 'http://www.example.com/'; - self::$DI['oauth2-app-user']->setWebsite($site); - $this->assertEquals($site, self::$DI['oauth2-app-user']->getWebsite()); - self::$DI['oauth2-app-user']->setWebsite(''); - $this->assertEquals('', self::$DI['oauth2-app-user']->getWebsite()); - - $this->assertInstanceOf('DateTime', self::$DI['oauth2-app-user']->getCreated()); - $this->assertInstanceOf('DateTime', self::$DI['oauth2-app-user']->getUpdated()); - - $this->assertMd5(self::$DI['oauth2-app-user']->getClientId()); - - $client_id = md5('prouto'); - self::$DI['oauth2-app-user']->seClientId($client_id); - $this->assertEquals($client_id, self::$DI['oauth2-app-user']->getClientId()); - $this->assertMd5(self::$DI['oauth2-app-user']->getClientId()); - - $this->assertMd5(self::$DI['oauth2-app-user']->getClientSecret()); - - $client_secret = md5('prouto'); - self::$DI['oauth2-app-user']->setClientSecret($client_secret); - $this->assertEquals($client_secret, self::$DI['oauth2-app-user']->getClientSecret()); - $this->assertMd5(self::$DI['oauth2-app-user']->getClientSecret()); - - $uri = 'http://www.example.com/callback/'; - self::$DI['oauth2-app-user']->setRedirectUri($uri); - $this->assertEquals($uri, self::$DI['oauth2-app-user']->getRedirectUri()); - } - - private function assertmd5($md5) - { - $this->assertTrue((count(preg_match('/[a-z0-9]{32}/', $md5)) === 1)); - } -} diff --git a/tests/classes/api/oauthv2/AuthCodeTest.php b/tests/classes/api/oauthv2/AuthCodeTest.php deleted file mode 100644 index f4dc108b5b..0000000000 --- a/tests/classes/api/oauthv2/AuthCodeTest.php +++ /dev/null @@ -1,70 +0,0 @@ - -account = self::$DI['app']['repo.api-accounts']->findByUserAndApplication(self::$DI['user'], self::$DI['oauth2-app-user']); - $expires = time() + 100; - $this->code = self::$DI['app']['random.low']->generateString(8); - $this->object = API_OAuth2_AuthCode::create(self::$DI['app'], $this->account, $this->code, $expires); - } - - public function testGet_code() - { - $this->assertEquals($this->code, $this->object->get_code()); - } - - public function testGet_account() - { - $this->assertInstanceOf('ApiApplication', $this->object->get_account()); - } - - public function testGet_redirect_uri() - { - $this->assertEquals('', $this->object->get_redirect_uri()); - } - - public function testSet_redirect_uri() - { - $redirect_uri = 'https://www.google.com'; - $this->assertEquals('', $this->object->get_redirect_uri()); - $this->object->set_redirect_uri($redirect_uri); - $this->assertEquals($redirect_uri, $this->object->get_redirect_uri()); - } - - public function testGet_expires() - { - $this->assertInternalType('string', $this->object->get_expires()); - } - - public function testGet_scope() - { - $this->assertEquals('', $this->object->get_scope()); - } - - public function testSet_scope() - { - $scope = 'prout'; - $this->assertEquals('', $this->object->get_scope()); - $this->object->set_scope($scope); - $this->assertEquals($scope, $this->object->get_scope()); - } - - public function testLoad_codes_by_account() - { - $this->assertTrue(is_array(API_OAuth2_AuthCode::load_codes_by_account(self::$DI['app'], $this->account))); - $this->assertTrue(count(API_OAuth2_AuthCode::load_codes_by_account(self::$DI['app'], $this->account)) > 0); - } -} diff --git a/tests/classes/api/oauthv2/RefreshTokenTest.php b/tests/classes/api/oauthv2/RefreshTokenTest.php deleted file mode 100644 index e9651461ca..0000000000 --- a/tests/classes/api/oauthv2/RefreshTokenTest.php +++ /dev/null @@ -1,95 +0,0 @@ -account = self::$DI['app']['repo.api-accounts']->findByUserAndApplication(self::$DI['user'], self::$DI['oauth2-app-user']); - $expires = time() + 100; - $this->token = self::$DI['app']['random.low']->generateString(8); - $this->scope = 'scopidou'; - - $this->object = API_OAuth2_RefreshToken::create(self::$DI['app'], $this->account, $expires, $this->token, $this->scope); - } - - public function testGet_value() - { - $this->assertEquals($this->token, $this->object->get_value()); - } - - /** - * @todo Implement testGet_account(). - */ - public function testGet_account() - { - // Remove the following lines when you implement this test. - $this->markTestIncomplete( - 'This test has not been implemented yet.' - ); - } - - /** - * @todo Implement testGet_expires(). - */ - public function testGet_expires() - { - // Remove the following lines when you implement this test. - $this->markTestIncomplete( - 'This test has not been implemented yet.' - ); - } - - /** - * @todo Implement testGet_scope(). - */ - public function testGet_scope() - { - // Remove the following lines when you implement this test. - $this->markTestIncomplete( - 'This test has not been implemented yet.' - ); - } - - /** - * @todo Implement testDelete(). - */ - public function testDelete() - { - // Remove the following lines when you implement this test. - $this->markTestIncomplete( - 'This test has not been implemented yet.' - ); - } - - /** - * @todo Implement testLoad_by_account(). - */ - public function testLoad_by_account() - { - // Remove the following lines when you implement this test. - $this->markTestIncomplete( - 'This test has not been implemented yet.' - ); - } - - /** - * @todo Implement testCreate(). - */ - public function testCreate() - { - // Remove the following lines when you implement this test. - $this->markTestIncomplete( - 'This test has not been implemented yet.' - ); - } -} diff --git a/tests/classes/api/oauthv2/TokenTest.php b/tests/classes/api/oauthv2/TokenTest.php deleted file mode 100644 index 5ba428ec9e..0000000000 --- a/tests/classes/api/oauthv2/TokenTest.php +++ /dev/null @@ -1,88 +0,0 @@ -findByUserAndApplication(self::$DI['user'], self::$DI['oauth2-app-user']); - - try { - new API_OAuth2_Token(self::$DI['app']['phraseanet.appbox'], $account, self::$DI['app']['random.medium']); - $this->fail(); - } catch (Exception $e) { - - } - - $this->object = API_OAuth2_Token::create(self::$DI['app']['phraseanet.appbox'], $account, self::$DI['app']['random.medium']); - } - - public function tearDown() - { - $this->object->delete(); - parent::tearDown(); - } - - private function assertmd5($md5) - { - $this->assertTrue((count(preg_match('/[a-z0-9]{32}/', $md5)) === 1)); - } - - public function testGettersAndSetters() - { - $this->assertmd5($this->object->get_value()); - - $value = md5('prout'); - $this->object->set_value($value); - $this->assertEquals($value, $this->object->get_value()); - - $this->object->set_session_id(null); - $this->assertNull($this->object->get_session_id()); - - $this->object->set_session_id(458); - $this->assertEquals(458, $this->object->get_session_id()); - - $expire = time() + 3600; - $this->object->set_expires($expire); - $diff = (int) $this->object->get_expires() - time(); - $this->assertSame($expire, $this->object->get_expires(), "expiration timestamp is string : " . $this->object->get_expires()); - $this->assertTrue($diff > 3500, "expire value $diff should be more than 3500 seconds "); - $this->assertTrue($diff < 3700, "expire value $diff should be less than 3700 seconds "); - - $date = time() + 7200; - $this->object->set_expires($date); - $this->assertEquals($date, $this->object->get_expires()); - - $this->assertNull($this->object->get_scope()); - - $this->assertNull($this->object->get_scope()); - $scope = "prout"; - $this->object->set_scope($scope); - $this->assertEquals($scope, $this->object->get_scope()); - - $this->assertInstanceOf('ApiApplication', $this->object->get_account()); - } - - public function testRenew() - { - $first = $this->object->get_value(); - $this->assertMd5($first); - $this->object->renew(); - $second = $this->object->get_value(); - $this->assertMd5($second); - $this->assertNotEquals($second, $first); - } - - public function testLoad_by_oauth_token() - { - $token = $this->object->get_value(); - $loaded = API_OAuth2_Token::load_by_oauth_token(self::$DI['app'], $token); - $this->assertInstanceOf('API_OAuth2_Token', $loaded); - $this->assertEquals($this->object, $loaded); - } -} From e818b7da29a610fd6c69deeccd741fbc86d912a2 Mon Sep 17 00:00:00 2001 From: Nicolas Le Goff Date: Thu, 6 Mar 2014 12:45:51 +0100 Subject: [PATCH 052/112] Replace method calls in template --- .../web/account/authorized_apps.html.twig | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/templates/web/account/authorized_apps.html.twig b/templates/web/account/authorized_apps.html.twig index f54981e521..f8c03ef778 100644 --- a/templates/web/account/authorized_apps.html.twig +++ b/templates/web/account/authorized_apps.html.twig @@ -14,18 +14,18 @@ {% if applications|length > 0 %}
    {% for application in applications %} -
  • +
  • - {% set account = application.get_user_account(app["authentication"].getUser()) %} - {{ "Revoquer l\'access" | trans }} - {{ "Authoriser l\'access" | trans }} + {% set account = application.getAccount() %} + {{ "Revoquer l\'access" | trans }} + {{ "Authoriser l\'access" | trans }}

    - - {{ application.get_name() }} + + {{ application.getName() }} - {% if application.get_creator() is not none %} + {% if application.getCreator() is not none %} - {% set user_name = application.get_creator().getDisplayName() %} + {% set user_name = application.getCreator().getDisplayName() %} {% trans with {'%user_name%' : user_name} %}par %user_name%{% endtrans %} {% endif%} @@ -34,7 +34,7 @@ {{ "Not Allowed" | trans }} {{ "Allowed" | trans }}

    -

    {{ application.get_description()|truncate(120, true, "...") }}

    +

    {{ application.getDescription()|truncate(120, true, "...") }}

  • {%endfor%} From 6f87cab8c10029b722c756d8fefd45112bc73fa2 Mon Sep 17 00:00:00 2001 From: Nicolas Le Goff Date: Thu, 6 Mar 2014 12:51:11 +0100 Subject: [PATCH 053/112] Fix annotation issue --- lib/Alchemy/Phrasea/Model/Entities/ApiAccount.php | 2 +- lib/Alchemy/Phrasea/Model/Entities/ApiApplication.php | 2 +- lib/Alchemy/Phrasea/Model/Entities/ApiOauthToken.php | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/Alchemy/Phrasea/Model/Entities/ApiAccount.php b/lib/Alchemy/Phrasea/Model/Entities/ApiAccount.php index 00f0cc98be..d2fa72f04d 100644 --- a/lib/Alchemy/Phrasea/Model/Entities/ApiAccount.php +++ b/lib/Alchemy/Phrasea/Model/Entities/ApiAccount.php @@ -50,7 +50,7 @@ class ApiAccount /** * @ORM\OneToOne(targetEntity="ApiOauthToken", inversedBy="account") - * @ORM\JoinColumn(name="oauth_token", referencedColumnName="id", nullable=true) + * @ORM\JoinColumn(name="oauth_token", referencedColumnName="oauth_token", nullable=true) * * @return ApiApplication **/ diff --git a/lib/Alchemy/Phrasea/Model/Entities/ApiApplication.php b/lib/Alchemy/Phrasea/Model/Entities/ApiApplication.php index d0a35787b4..5a935b90b4 100644 --- a/lib/Alchemy/Phrasea/Model/Entities/ApiApplication.php +++ b/lib/Alchemy/Phrasea/Model/Entities/ApiApplication.php @@ -121,7 +121,7 @@ class ApiApplication /** - * @OneToMany(targetEntity="ApiAccount", mappedBy="product", cascade={"remove"}) + * @ORM\OneToMany(targetEntity="ApiAccount", mappedBy="product", cascade={"remove"}) **/ private $accounts; diff --git a/lib/Alchemy/Phrasea/Model/Entities/ApiOauthToken.php b/lib/Alchemy/Phrasea/Model/Entities/ApiOauthToken.php index df8f1ba71e..c9fa54f42b 100644 --- a/lib/Alchemy/Phrasea/Model/Entities/ApiOauthToken.php +++ b/lib/Alchemy/Phrasea/Model/Entities/ApiOauthToken.php @@ -27,7 +27,8 @@ class ApiOauthToken private $session; /** - * @OneToOne(targetEntity="ApiAccount", mappedBy="oauthToken", nullable=false) + * @ORM\OneToOne(targetEntity="ApiAccount", mappedBy="oauthToken") + * @ORM\JoinColumn(name="account_id", referencedColumnName="id", nullable=false) * * @return ApiAccount **/ From 7b85b03d1308ff5b7d653b82f67dee0518bb578e Mon Sep 17 00:00:00 2001 From: Nicolas Le Goff Date: Thu, 6 Mar 2014 14:54:14 +0100 Subject: [PATCH 054/112] Set api version when creating new account --- lib/Alchemy/Phrasea/Model/Manipulator/ApiAccountManipulator.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/Alchemy/Phrasea/Model/Manipulator/ApiAccountManipulator.php b/lib/Alchemy/Phrasea/Model/Manipulator/ApiAccountManipulator.php index 8382c1960d..399cac4847 100644 --- a/lib/Alchemy/Phrasea/Model/Manipulator/ApiAccountManipulator.php +++ b/lib/Alchemy/Phrasea/Model/Manipulator/ApiAccountManipulator.php @@ -13,6 +13,7 @@ namespace Alchemy\Phrasea\Model\Manipulator; use Alchemy\Phrasea\Application; use Alchemy\Phrasea\Authentication\ACLProvider; +use Alchemy\Phrasea\Controller\Api\V1; use Alchemy\Phrasea\Model\Entities\ApiAccount; use Alchemy\Phrasea\Model\Entities\ApiApplication; use Alchemy\Phrasea\Model\Entities\User; @@ -35,6 +36,7 @@ class ApiAccountManipulator implements ManipulatorInterface $account = new ApiAccount(); $account->setUser($user); $account->setApplication($application); + $account->setApiVersion(V1::VERSION); $this->update($account); From 652b5fb08fcc4f0738161e302c04c94e8f34a317 Mon Sep 17 00:00:00 2001 From: Nicolas Le Goff Date: Thu, 6 Mar 2014 14:58:39 +0100 Subject: [PATCH 055/112] Expires can be null --- lib/Alchemy/Phrasea/Model/Entities/ApiOauthToken.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Alchemy/Phrasea/Model/Entities/ApiOauthToken.php b/lib/Alchemy/Phrasea/Model/Entities/ApiOauthToken.php index c9fa54f42b..48f73a8f19 100644 --- a/lib/Alchemy/Phrasea/Model/Entities/ApiOauthToken.php +++ b/lib/Alchemy/Phrasea/Model/Entities/ApiOauthToken.php @@ -105,7 +105,7 @@ class ApiOauthToken * * @return ApiOauthTokens */ - public function setExpires(\DateTime $expires) + public function setExpires(\DateTime $expires = null) { $this->expires = $expires; From c4165ab03e7f2a5c27c690592fd8882f7c50f441 Mon Sep 17 00:00:00 2001 From: Nicolas Le Goff Date: Thu, 6 Mar 2014 14:59:22 +0100 Subject: [PATCH 056/112] Session can be null --- lib/Alchemy/Phrasea/Model/Entities/ApiOauthToken.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Alchemy/Phrasea/Model/Entities/ApiOauthToken.php b/lib/Alchemy/Phrasea/Model/Entities/ApiOauthToken.php index 48f73a8f19..455cffd5de 100644 --- a/lib/Alchemy/Phrasea/Model/Entities/ApiOauthToken.php +++ b/lib/Alchemy/Phrasea/Model/Entities/ApiOauthToken.php @@ -165,7 +165,7 @@ class ApiOauthToken * * @return ApiOauthTokens */ - public function setSession(Session $session) + public function setSession(Session $session = null) { $this->session = $session; From a55a266de2a6bda8e6004db2736cedc29d210b86 Mon Sep 17 00:00:00 2001 From: Nicolas Le Goff Date: Thu, 6 Mar 2014 15:06:12 +0100 Subject: [PATCH 057/112] Fix ID genration startegy --- lib/Alchemy/Phrasea/Model/Entities/ApiOauthCode.php | 1 - lib/Alchemy/Phrasea/Model/Entities/ApiOauthRefreshToken.php | 1 - lib/Alchemy/Phrasea/Model/Entities/ApiOauthToken.php | 1 - 3 files changed, 3 deletions(-) diff --git a/lib/Alchemy/Phrasea/Model/Entities/ApiOauthCode.php b/lib/Alchemy/Phrasea/Model/Entities/ApiOauthCode.php index 4cbe575b20..9e1cc7d4fa 100644 --- a/lib/Alchemy/Phrasea/Model/Entities/ApiOauthCode.php +++ b/lib/Alchemy/Phrasea/Model/Entities/ApiOauthCode.php @@ -16,7 +16,6 @@ class ApiOauthCode * * @ORM\Column(name="code", type="string", length=16, nullable=false) * @ORM\Id - * @ORM\GeneratedValue(strategy="IDENTITY") */ private $code; diff --git a/lib/Alchemy/Phrasea/Model/Entities/ApiOauthRefreshToken.php b/lib/Alchemy/Phrasea/Model/Entities/ApiOauthRefreshToken.php index e19d177b99..273399ee97 100644 --- a/lib/Alchemy/Phrasea/Model/Entities/ApiOauthRefreshToken.php +++ b/lib/Alchemy/Phrasea/Model/Entities/ApiOauthRefreshToken.php @@ -16,7 +16,6 @@ class ApiOauthRefreshToken * * @ORM\Column(name="refresh_token", type="string", length=128, nullable=false) * @ORM\Id - * @ORM\GeneratedValue(strategy="IDENTITY") */ private $refreshToken; diff --git a/lib/Alchemy/Phrasea/Model/Entities/ApiOauthToken.php b/lib/Alchemy/Phrasea/Model/Entities/ApiOauthToken.php index 455cffd5de..34806b5938 100644 --- a/lib/Alchemy/Phrasea/Model/Entities/ApiOauthToken.php +++ b/lib/Alchemy/Phrasea/Model/Entities/ApiOauthToken.php @@ -16,7 +16,6 @@ class ApiOauthToken * * @ORM\Column(name="oauth_token", type="string", length=32, nullable=false) * @ORM\Id - * @ORM\GeneratedValue(strategy="IDENTITY") */ private $oauthToken; From 6f05d704c1c9ac52c4bc8648db09b4dfcf6bb253 Mon Sep 17 00:00:00 2001 From: Nicolas Le Goff Date: Thu, 6 Mar 2014 15:18:14 +0100 Subject: [PATCH 058/112] Fix method calls --- .../web/developers/application.html.twig | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/templates/web/developers/application.html.twig b/templates/web/developers/application.html.twig index e9eac5c9d0..f602b9ef7e 100644 --- a/templates/web/developers/application.html.twig +++ b/templates/web/developers/application.html.twig @@ -10,11 +10,11 @@

    {{ "Application" | trans }}

    - +
    - -
    {{ application.get_description() }}
    + +
    {{ application.getDescription() }}

    {{ "settings OAuth" | trans }}

    @@ -24,22 +24,22 @@ Client ID - {{ application.get_client_id() }} + {{ application.getClientId() }} Client Secret - {{ application.get_client_secret() }} + {{ application.getClientSecret() }} {{ "URL de callback" | trans }} - {% if application.get_type() == constant("Alchemy\Phrasea\Model\Entities\ApiApplication::DESKTOP_TYPE") %} + {% if application.getType() == constant("Alchemy\Phrasea\Model\Entities\ApiApplication::DESKTOP_TYPE") %} - {{ application.get_redirect_uri() }} + {{ application.getRedirectUri() }} {% else %} - {{ application.get_redirect_uri() }} - -

    {{ application.get_name() }}

    +

    {{ application.getName() }}

    - - {{ application.get_name() }} + + {{ application.getName() }}

    -

    {{ application.get_description()|truncate(120, true, "...") }}

    +

    {{ application.getDescription()|truncate(120, true, "...") }}

    {%endfor%}
From 80bf2a2f572ad00c05d34d7721290897455250b3 Mon Sep 17 00:00:00 2001 From: Nicolas Le Goff Date: Thu, 6 Mar 2014 17:16:54 +0100 Subject: [PATCH 074/112] Fix tests --- .../Phrasea/Controller/Api/ApiTestCase.php | 189 ++++++------------ .../Phrasea/Controller/Api/OAuth2Test.php | 62 +----- .../Phrasea/Controller/Root/AccountTest.php | 2 - .../Controller/Root/DevelopersTest.php | 32 +-- tests/classes/PhraseanetTestCase.php | 12 +- 5 files changed, 95 insertions(+), 202 deletions(-) diff --git a/tests/Alchemy/Tests/Phrasea/Controller/Api/ApiTestCase.php b/tests/Alchemy/Tests/Phrasea/Controller/Api/ApiTestCase.php index dda387dacf..3fe693c693 100644 --- a/tests/Alchemy/Tests/Phrasea/Controller/Api/ApiTestCase.php +++ b/tests/Alchemy/Tests/Phrasea/Controller/Api/ApiTestCase.php @@ -18,33 +18,6 @@ use Symfony\Component\HttpFoundation\Response; abstract class ApiTestCase extends \PhraseanetWebTestCase { - /** - * @var ApiOauthToken - */ - private static $token; - - /** - * @var ApiAccount - */ - private static $account; - /** - * @var ApiApplication - */ - private static $oauthApplication; - /** - * @var ApiOauthToken - */ - private static $adminToken; - /** - * @var ApiAccount - */ - private static $adminAccount; - /** - * @var ApiApplication - */ - private static $adminApplication; - private static $apiInitialized = false; - abstract protected function getParameters(array $parameters = []); abstract protected function unserialize($data); abstract protected function getAcceptMimeType(); @@ -62,27 +35,6 @@ abstract class ApiTestCase extends \PhraseanetWebTestCase self::$DI['app'] = self::$DI->share(function ($DI) { return $this->loadApp('lib/Alchemy/Phrasea/Application/Api.php'); }); - - if (!self::$apiInitialized) { - self::$account = self::$DI['app']['repo.api-accounts']->findByUserAndApplication(self::$DI['user_notAdmin'], self::$DI['oauth2-app-user-not-admin']); - self::$DI['app']['manipulator.api-account']->revokeAccess(self::$account); - self::$token = self::$account->getOAuthToken()->getOauthToken(); - - self::$adminAccount = self::$DI['app']['repo.api-accounts']->findByUserAndApplication(self::$DI['user'], self::$DI['oauth2-app-user']); - self::$DI['app']['manipulator.api-account']->revokeAccess(self::$adminAccount); - self::$adminAccount = self::$adminAccount->getOAuthToken()->getOauthToken(); - - self::$apiInitialized = true; - } - } - - public static function tearDownAfterClass() - { - self::$apiInitialized = false; - self::$token = self::$account = self::$oauthApplication = self::$adminToken - = self::$adminAccount = self::$adminApplication = null; - - parent::tearDownAfterClass(); } /** @@ -99,7 +51,7 @@ abstract class ApiTestCase extends \PhraseanetWebTestCase } }); - $this->setToken(self::$token); + $this->setToken(self::$DI['oauth2-app-acc-user-not-admin']->getOauthToken()->getOauthToken()); self::$DI['client']->request('GET', $route, $this->getParameters(), [], ['HTTP_Accept' => $this->getAcceptMimeType()]); $this->assertEquals(1, $preEvent); @@ -108,7 +60,7 @@ abstract class ApiTestCase extends \PhraseanetWebTestCase public function testThatSessionIsClosedAfterRequest() { $this->assertCount(0, self::$DI['app']['EM']->getRepository('Phraseanet:Session')->findAll()); - $this->setToken(self::$token); + $this->setToken(self::$DI['oauth2-app-acc-user-not-admin']->getOauthToken()->getOauthToken()); self::$DI['client']->request('GET', '/api/v1/databoxes/list/', $this->getParameters(), [], ['HTTP_Accept' => $this->getAcceptMimeType()]); $this->assertCount(0, self::$DI['app']['EM']->getRepository('Phraseanet:Session')->findAll()); } @@ -127,7 +79,7 @@ abstract class ApiTestCase extends \PhraseanetWebTestCase public function testRouteNotFound() { $route = '/api/v1/nothinghere'; - $this->setToken(self::$token); + $this->setToken(self::$DI['oauth2-app-acc-user-not-admin']->getOauthToken()->getOauthToken()); self::$DI['client']->request('GET', $route, $this->getParameters(), [], ['HTTP_Accept' => $this->getAcceptMimeType()]); $content = $this->unserialize(self::$DI['client']->getResponse()->getContent()); @@ -137,7 +89,7 @@ abstract class ApiTestCase extends \PhraseanetWebTestCase public function testDataboxListRoute() { - $this->setToken(self::$token); + $this->setToken(self::$DI['oauth2-app-acc-user-not-admin']->getOauthToken()->getOauthToken()); self::$DI['client']->request('GET', '/api/v1/databoxes/list/', $this->getParameters(), [], ['HTTP_Accept' => $this->getAcceptMimeType()]); $content = $this->unserialize(self::$DI['client']->getResponse()->getContent()); @@ -186,7 +138,7 @@ abstract class ApiTestCase extends \PhraseanetWebTestCase $fail = $e; } - self::$DI['app']['conf']->set(['registry', 'api-clients', 'navigator-enabled'], false); + self::$DI['app']['conf']->set(['registry', 'api-clients', 'navigator-enabled'], $value); if ($fail) { throw $fail; @@ -198,7 +150,7 @@ abstract class ApiTestCase extends \PhraseanetWebTestCase */ public function testAdminOnlyShedulerState() { - $this->setToken(self::$token); + $this->setToken(self::$DI['oauth2-app-acc-user-not-admin']->getOauthToken()->getOauthToken()); self::$DI['client']->request('GET', '/api/v1/monitor/tasks/', $this->getParameters(), [], ['HTTP_Accept' => $this->getAcceptMimeType()]); $content = $this->unserialize(self::$DI['client']->getResponse()->getContent()); @@ -234,10 +186,7 @@ abstract class ApiTestCase extends \PhraseanetWebTestCase */ public function testGetMonitorTasks() { - if (null === self::$adminToken) { - $this->markTestSkipped('there is no user with admin rights'); - } - $this->setToken(self::$adminToken); + $this->setToken(self::$DI['oauth2-app-acc-user']->getOauthToken()->getOauthToken()); $route = '/api/v1/monitor/tasks/'; $this->evaluateMethodNotAllowedRoute($route, ['POST', 'PUT', 'DELETE']); @@ -262,10 +211,7 @@ abstract class ApiTestCase extends \PhraseanetWebTestCase */ public function testGetScheduler() { - if (null === self::$adminToken) { - $this->markTestSkipped('there is no user with admin rights'); - } - $this->setToken(self::$adminToken); + $this->setToken(self::$DI['oauth2-app-acc-user']->getOauthToken()->getOauthToken()); $route = '/api/v1/monitor/scheduler/'; $this->evaluateMethodNotAllowedRoute($route, ['POST', 'PUT', 'DELETE']); @@ -341,15 +287,11 @@ abstract class ApiTestCase extends \PhraseanetWebTestCase { $tasks = self::$DI['app']['repo.tasks']->findAll(); - if (null === self::$adminToken) { - $this->markTestSkipped('there is no user with admin rights'); - } - if (!count($tasks)) { $this->markTestSkipped('no tasks created for the current instance'); } - $this->setToken(self::$adminToken); + $this->setToken(self::$DI['oauth2-app-acc-user']->getOauthToken()->getOauthToken()); $idTask = $tasks[0]->getId(); $route = '/api/v1/monitor/task/' . $idTask . '/'; @@ -368,15 +310,11 @@ abstract class ApiTestCase extends \PhraseanetWebTestCase { $tasks = self::$DI['app']['repo.tasks']->findAll(); - if (null === self::$adminToken) { - $this->markTestSkipped('there is no user with admin rights'); - } - if (!count($tasks)) { $this->markTestSkipped('no tasks created for the current instance'); } - $this->setToken(self::$adminToken); + $this->setToken(self::$DI['oauth2-app-acc-user']->getOauthToken()->getOauthToken()); $idTask = $tasks[0]->getId(); $route = '/api/v1/monitor/task/' . $idTask . '/'; @@ -396,10 +334,10 @@ abstract class ApiTestCase extends \PhraseanetWebTestCase public function testUnknowGetMonitorTaskById() { - if (null === self::$adminToken) { + if (null === self::$DI['oauth2-app-acc-user']->getOauthToken()->getOauthToken()) { $this->markTestSkipped('no tasks created for the current instance'); } - $this->setToken(self::$adminToken); + $this->setToken(self::$DI['oauth2-app-acc-user']->getOauthToken()->getOauthToken()); self::$DI['client']->followRedirects(); self::$DI['client']->request('GET', '/api/v1/monitor/task/0/', $this->getParameters(), [], ['HTTP_Accept' => $this->getAcceptMimeType()]); $content = $this->unserialize(self::$DI['client']->getResponse()->getContent()); @@ -408,17 +346,13 @@ abstract class ApiTestCase extends \PhraseanetWebTestCase public function testPostMonitorStartTask() { - if (null === self::$adminToken) { - $this->markTestSkipped('there is no user with admin rights'); - } - $tasks = self::$DI['app']['repo.tasks']->findAll(); if (!count($tasks)) { $this->markTestSkipped('no tasks created for the current instance'); } - $this->setToken(self::$adminToken); + $this->setToken(self::$DI['oauth2-app-acc-user']->getOauthToken()->getOauthToken()); $idTask = $tasks[0]->getId(); $route = '/api/v1/monitor/task/' . $idTask . '/start/'; @@ -440,15 +374,11 @@ abstract class ApiTestCase extends \PhraseanetWebTestCase { $tasks = self::$DI['app']['repo.tasks']->findAll(); - if (null === self::$adminToken) { - $this->markTestSkipped('there is no user with admin rights'); - } - if (!count($tasks)) { $this->markTestSkipped('no tasks created for the current instance'); } - $this->setToken(self::$adminToken); + $this->setToken(self::$DI['oauth2-app-acc-user']->getOauthToken()->getOauthToken()); $idTask = $tasks[0]->getId(); $route = '/api/v1/monitor/task/' . $idTask . '/stop/'; @@ -468,12 +398,9 @@ abstract class ApiTestCase extends \PhraseanetWebTestCase public function testgetMonitorPhraseanet() { - if (null === self::$adminToken) { - $this->markTestSkipped('there is no user with admin rights'); - } self::$DI['app']['phraseanet.SE'] = $this->createSearchEngineMock(); - $this->setToken(self::$adminToken); + $this->setToken(self::$DI['oauth2-app-acc-user']->getOauthToken()->getOauthToken()); self::$DI['client']->request('GET', '/api/v1/monitor/phraseanet/', $this->getParameters(), [], ['HTTP_Accept' => $this->getAcceptMimeType()]); $content = $this->unserialize(self::$DI['client']->getResponse()->getContent()); @@ -491,7 +418,7 @@ abstract class ApiTestCase extends \PhraseanetWebTestCase public function testRecordRoute() { - $this->setToken(self::$token); + $this->setToken(self::$DI['oauth2-app-acc-user-not-admin']->getOauthToken()->getOauthToken()); $route = '/api/v1/records/' . self::$DI['record_1']->get_sbas_id() . '/' . self::$DI['record_1']->get_record_id() . '/'; $this->evaluateMethodNotAllowedRoute($route, ['POST', 'PUT', 'DELETE']); @@ -513,7 +440,7 @@ abstract class ApiTestCase extends \PhraseanetWebTestCase public function testStoryRoute() { - $this->setToken(self::$token); + $this->setToken(self::$DI['oauth2-app-acc-user-not-admin']->getOauthToken()->getOauthToken()); self::$DI['app']['session']->set('usr_id', self::$DI['user']->getId()); if (false === self::$DI['record_story_1']->hasChild(self::$DI['record_1'])) { self::$DI['record_story_1']->appendChild(self::$DI['record_1']); @@ -544,7 +471,7 @@ abstract class ApiTestCase extends \PhraseanetWebTestCase public function testDataboxCollectionRoute() { - $this->setToken(self::$token); + $this->setToken(self::$DI['oauth2-app-acc-user-not-admin']->getOauthToken()->getOauthToken()); $databox_id = self::$DI['record_1']->get_sbas_id(); $route = '/api/v1/databoxes/' . $databox_id . '/collections/'; $this->evaluateMethodNotAllowedRoute($route, ['POST', 'PUT', 'DELETE']); @@ -584,7 +511,7 @@ abstract class ApiTestCase extends \PhraseanetWebTestCase public function testDataboxStatusRoute() { - $this->setToken(self::$token); + $this->setToken(self::$DI['oauth2-app-acc-user-not-admin']->getOauthToken()->getOauthToken()); $databox_id = self::$DI['record_1']->get_sbas_id(); $databox = self::$DI['app']['phraseanet.appbox']->get_databox($databox_id); $ref_status = $databox->get_statusbits(); @@ -633,7 +560,7 @@ abstract class ApiTestCase extends \PhraseanetWebTestCase public function testDataboxMetadatasRoute() { - $this->setToken(self::$token); + $this->setToken(self::$DI['oauth2-app-acc-user-not-admin']->getOauthToken()->getOauthToken()); $databox_id = self::$DI['record_1']->get_sbas_id(); $databox = self::$DI['app']['phraseanet.appbox']->get_databox($databox_id); $ref_structure = $databox->get_meta_structure(); @@ -716,7 +643,7 @@ abstract class ApiTestCase extends \PhraseanetWebTestCase public function testDataboxTermsOfUseRoute() { - $this->setToken(self::$token); + $this->setToken(self::$DI['oauth2-app-acc-user-not-admin']->getOauthToken()->getOauthToken()); $databox_id = self::$DI['record_1']->get_sbas_id(); $route = '/api/v1/databoxes/' . $databox_id . '/termsOfUse/'; $this->evaluateMethodNotAllowedRoute($route, ['POST', 'PUT', 'DELETE']); @@ -755,7 +682,7 @@ abstract class ApiTestCase extends \PhraseanetWebTestCase self::$DI['app']['manipulator.user']->expects($this->once())->method('logQuery'); - $this->setToken(self::$token); + $this->setToken(self::$DI['oauth2-app-acc-user-not-admin']->getOauthToken()->getOauthToken()); self::$DI['client']->request('POST', '/api/v1/search/', $this->getParameters(), [], ['HTTP_Accept' => $this->getAcceptMimeType()]); $content = $this->unserialize(self::$DI['client']->getResponse()->getContent()); @@ -788,7 +715,7 @@ abstract class ApiTestCase extends \PhraseanetWebTestCase $this->markTestSkipped('Phrasea2 extension is required for this test'); } - $this->setToken(self::$token); + $this->setToken(self::$DI['oauth2-app-acc-user-not-admin']->getOauthToken()->getOauthToken()); self::$DI['record_story_1']; @@ -824,7 +751,7 @@ abstract class ApiTestCase extends \PhraseanetWebTestCase $this->markTestSkipped('Phrasea2 extension is required for this test'); } - $this->setToken(self::$token); + $this->setToken(self::$DI['oauth2-app-acc-user-not-admin']->getOauthToken()->getOauthToken()); self::$DI['client']->request('POST', '/api/v1/records/search/', $this->getParameters(), [], ['HTTP_Accept' => $this->getAcceptMimeType()]); $content = $this->unserialize(self::$DI['client']->getResponse()->getContent()); @@ -846,7 +773,7 @@ abstract class ApiTestCase extends \PhraseanetWebTestCase */ public function testRecordsSearchRouteWithQuery($method) { - $this->setToken(self::$token); + $this->setToken(self::$DI['oauth2-app-acc-user-not-admin']->getOauthToken()->getOauthToken()); $searchEngine = $this->getMockBuilder('Alchemy\Phrasea\SearchEngine\SearchEngineResult') ->disableOriginalConstructor() ->getMock(); @@ -875,7 +802,7 @@ abstract class ApiTestCase extends \PhraseanetWebTestCase public function testRecordsCaptionRoute() { - $this->setToken(self::$token); + $this->setToken(self::$DI['oauth2-app-acc-user-not-admin']->getOauthToken()->getOauthToken()); self::$DI['app']['phraseanet.SE'] = $this->createSearchEngineMock(); $this->injectMetadatas(self::$DI['record_1']); @@ -901,7 +828,7 @@ abstract class ApiTestCase extends \PhraseanetWebTestCase public function testRecordsMetadatasRoute() { - $this->setToken(self::$token); + $this->setToken(self::$DI['oauth2-app-acc-user-not-admin']->getOauthToken()->getOauthToken()); $route = '/api/v1/records/' . self::$DI['record_1']->get_sbas_id() . '/' . self::$DI['record_1']->get_record_id() . '/metadatas/'; $this->evaluateMethodNotAllowedRoute($route, ['POST', 'PUT', 'DELETE']); @@ -924,7 +851,7 @@ abstract class ApiTestCase extends \PhraseanetWebTestCase public function testRecordsStatusRoute() { - $this->setToken(self::$token); + $this->setToken(self::$DI['oauth2-app-acc-user-not-admin']->getOauthToken()->getOauthToken()); $route = '/api/v1/records/' . self::$DI['record_1']->get_sbas_id() . '/' . self::$DI['record_1']->get_record_id() . '/status/'; $this->evaluateMethodNotAllowedRoute($route, ['POST', 'PUT', 'DELETE']); @@ -947,7 +874,7 @@ abstract class ApiTestCase extends \PhraseanetWebTestCase public function testRecordsEmbedRoute() { - $this->setToken(self::$token); + $this->setToken(self::$DI['oauth2-app-acc-user-not-admin']->getOauthToken()->getOauthToken()); $route = '/api/v1/records/' . self::$DI['record_1']->get_sbas_id() . '/' . self::$DI['record_1']->get_record_id() . '/embed/'; $this->evaluateMethodNotAllowedRoute($route, ['POST', 'PUT', 'DELETE']); @@ -973,7 +900,7 @@ abstract class ApiTestCase extends \PhraseanetWebTestCase public function testStoriesEmbedRoute() { - $this->setToken(self::$token); + $this->setToken(self::$DI['oauth2-app-acc-user-not-admin']->getOauthToken()->getOauthToken()); $story = self::$DI['record_story_1']; $route = '/api/v1/stories/' . $story->get_sbas_id() . '/' . $story->get_record_id() . '/embed/'; @@ -1000,7 +927,7 @@ abstract class ApiTestCase extends \PhraseanetWebTestCase public function testRecordsEmbedRouteMimeType() { - $this->setToken(self::$token); + $this->setToken(self::$DI['oauth2-app-acc-user-not-admin']->getOauthToken()->getOauthToken()); $route = '/api/v1/records/' . self::$DI['record_1']->get_sbas_id() . '/' . self::$DI['record_1']->get_record_id() . '/embed/'; @@ -1014,7 +941,7 @@ abstract class ApiTestCase extends \PhraseanetWebTestCase public function testRecordsEmbedRouteDevices() { - $this->setToken(self::$token); + $this->setToken(self::$DI['oauth2-app-acc-user-not-admin']->getOauthToken()->getOauthToken()); $route = '/api/v1/records/' . self::$DI['record_1']->get_sbas_id() . '/' . self::$DI['record_1']->get_record_id() . '/embed/'; @@ -1026,7 +953,7 @@ abstract class ApiTestCase extends \PhraseanetWebTestCase public function testRecordsRelatedRoute() { - $this->setToken(self::$token); + $this->setToken(self::$DI['oauth2-app-acc-user-not-admin']->getOauthToken()->getOauthToken()); $route = '/api/v1/records/' . self::$DI['record_1']->get_sbas_id() . '/' . self::$DI['record_1']->get_record_id() . '/related/'; $this->evaluateMethodNotAllowedRoute($route, ['POST', 'PUT', 'DELETE']); @@ -1053,7 +980,7 @@ abstract class ApiTestCase extends \PhraseanetWebTestCase public function testRecordsSetMetadatas() { self::$DI['app']['phraseanet.SE'] = $this->createSearchEngineMock(); - $this->setToken(self::$token); + $this->setToken(self::$DI['oauth2-app-acc-user-not-admin']->getOauthToken()->getOauthToken()); $record = self::$DI['record_1']; @@ -1111,7 +1038,7 @@ abstract class ApiTestCase extends \PhraseanetWebTestCase public function testRecordsSetStatus() { self::$DI['app']['phraseanet.SE'] = $this->createSearchEngineMock(); - $this->setToken(self::$token); + $this->setToken(self::$DI['oauth2-app-acc-user-not-admin']->getOauthToken()->getOauthToken()); $route = '/api/v1/records/' . self::$DI['record_1']->get_sbas_id() . '/' . self::$DI['record_1']->get_record_id() . '/setstatus/'; @@ -1173,7 +1100,7 @@ abstract class ApiTestCase extends \PhraseanetWebTestCase $file = new File(self::$DI['app'], self::$DI['app']['mediavorus']->guess(__DIR__ . '/../../../../../files/test001.jpg'), self::$DI['collection']); $record = \record_adapter::createFromFile($file, self::$DI['app']); - $this->setToken(self::$token); + $this->setToken(self::$DI['oauth2-app-acc-user-not-admin']->getOauthToken()->getOauthToken()); $route = '/api/v1/records/' . $record->get_sbas_id() . '/' . $record->get_record_id() . '/setcollection/'; @@ -1201,7 +1128,7 @@ abstract class ApiTestCase extends \PhraseanetWebTestCase public function testSearchBaskets() { - $this->setToken(self::$adminToken); + $this->setToken(self::$DI['oauth2-app-acc-user']->getOauthToken()->getOauthToken()); $route = '/api/v1/baskets/list/'; $this->evaluateMethodNotAllowedRoute($route, ['POST', 'PUT', 'DELETE']); @@ -1219,7 +1146,7 @@ abstract class ApiTestCase extends \PhraseanetWebTestCase public function testAddBasket() { - $this->setToken(self::$token); + $this->setToken(self::$DI['oauth2-app-acc-user-not-admin']->getOauthToken()->getOauthToken()); $route = '/api/v1/baskets/add/'; @@ -1239,7 +1166,7 @@ abstract class ApiTestCase extends \PhraseanetWebTestCase public function testBasketContent() { - $this->setToken(self::$adminToken); + $this->setToken(self::$DI['oauth2-app-acc-user']->getOauthToken()->getOauthToken()); $basketElement = self::$DI['app']['EM']->find('Phraseanet:BasketElement', 1); $basket = $basketElement->getBasket(); @@ -1274,7 +1201,7 @@ abstract class ApiTestCase extends \PhraseanetWebTestCase public function testSetBasketTitle() { - $this->setToken(self::$adminToken); + $this->setToken(self::$DI['oauth2-app-acc-user']->getOauthToken()->getOauthToken()); $basket = self::$DI['app']['EM']->find('Phraseanet:Basket', 1); @@ -1322,7 +1249,7 @@ abstract class ApiTestCase extends \PhraseanetWebTestCase public function testSetBasketDescription() { - $this->setToken(self::$adminToken); + $this->setToken(self::$DI['oauth2-app-acc-user']->getOauthToken()->getOauthToken()); $basket = self::$DI['app']['EM']->find('Phraseanet:Basket', 1); @@ -1345,7 +1272,7 @@ abstract class ApiTestCase extends \PhraseanetWebTestCase public function testDeleteBasket() { - $this->setToken(self::$adminToken); + $this->setToken(self::$DI['oauth2-app-acc-user']->getOauthToken()->getOauthToken()); $route = '/api/v1/baskets/1/delete/'; $this->evaluateMethodNotAllowedRoute($route, ['GET', 'PUT', 'DELETE']); @@ -1371,7 +1298,7 @@ abstract class ApiTestCase extends \PhraseanetWebTestCase public function testAddRecord() { self::$DI['app']['phraseanet.SE'] = $this->createSearchEngineMock(); - $this->setToken(self::$token); + $this->setToken(self::$DI['oauth2-app-acc-user-not-admin']->getOauthToken()->getOauthToken()); $route = '/api/v1/records/add/'; $params = $this->getAddRecordParameters(); @@ -1391,7 +1318,7 @@ abstract class ApiTestCase extends \PhraseanetWebTestCase public function testAddRecordForceRecord() { self::$DI['app']['phraseanet.SE'] = $this->createSearchEngineMock(); - $this->setToken(self::$token); + $this->setToken(self::$DI['oauth2-app-acc-user-not-admin']->getOauthToken()->getOauthToken()); $route = '/api/v1/records/add/'; $params = $this->getAddRecordParameters(); @@ -1416,7 +1343,7 @@ abstract class ApiTestCase extends \PhraseanetWebTestCase public function testAddRecordForceLazaret() { - $this->setToken(self::$token); + $this->setToken(self::$DI['oauth2-app-acc-user-not-admin']->getOauthToken()->getOauthToken()); $route = '/api/v1/records/add/'; $params = $this->getAddRecordParameters(); @@ -1440,7 +1367,7 @@ abstract class ApiTestCase extends \PhraseanetWebTestCase public function testAddRecordWrongBehavior() { - $this->setToken(self::$token); + $this->setToken(self::$DI['oauth2-app-acc-user-not-admin']->getOauthToken()->getOauthToken()); $route = '/api/v1/records/add/'; $params = $this->getAddRecordParameters(); @@ -1455,7 +1382,7 @@ abstract class ApiTestCase extends \PhraseanetWebTestCase public function testAddRecordWrongBaseId() { - $this->setToken(self::$adminToken); + $this->setToken(self::$DI['oauth2-app-acc-user']->getOauthToken()->getOauthToken()); $route = '/api/v1/records/add/'; $params = $this->getAddRecordParameters(); @@ -1470,7 +1397,7 @@ abstract class ApiTestCase extends \PhraseanetWebTestCase public function testAddRecordNoBaseId() { - $this->setToken(self::$token); + $this->setToken(self::$DI['oauth2-app-acc-user-not-admin']->getOauthToken()->getOauthToken()); $route = '/api/v1/records/add/'; $params = $this->getAddRecordParameters(); @@ -1485,7 +1412,7 @@ abstract class ApiTestCase extends \PhraseanetWebTestCase public function testAddRecordMultipleFiles() { - $this->setToken(self::$token); + $this->setToken(self::$DI['oauth2-app-acc-user-not-admin']->getOauthToken()->getOauthToken()); $route = '/api/v1/records/add/'; $file = [ @@ -1502,7 +1429,7 @@ abstract class ApiTestCase extends \PhraseanetWebTestCase public function testAddRecordNofile() { - $this->setToken(self::$token); + $this->setToken(self::$DI['oauth2-app-acc-user-not-admin']->getOauthToken()->getOauthToken()); $route = '/api/v1/records/add/'; self::$DI['client']->request('POST', $route, $this->getParameters($this->getAddRecordParameters()), [], ['HTTP_Accept' => $this->getAcceptMimeType()]); @@ -1516,7 +1443,7 @@ abstract class ApiTestCase extends \PhraseanetWebTestCase { $created_feed = self::$DI['app']['EM']->find('Phraseanet:Feed', 1); - $this->setToken(self::$token); + $this->setToken(self::$DI['oauth2-app-acc-user-not-admin']->getOauthToken()->getOauthToken()); $route = '/api/v1/feeds/list/'; $this->evaluateMethodNotAllowedRoute($route, ['POST', 'PUT', 'DELETE']); @@ -1567,7 +1494,7 @@ abstract class ApiTestCase extends \PhraseanetWebTestCase self::$DI['app']['EM']->persist($created_entry); self::$DI['app']['EM']->flush(); - $this->setToken(self::$token); + $this->setToken(self::$DI['oauth2-app-acc-user-not-admin']->getOauthToken()->getOauthToken()); $route = '/api/v1/feeds/content/'; $this->evaluateMethodNotAllowedRoute($route, ['POST', 'PUT', 'DELETE']); @@ -1612,7 +1539,7 @@ abstract class ApiTestCase extends \PhraseanetWebTestCase $feed = self::$DI['app']['EM']->find('Phraseanet:Feed', 1); $created_entry = $feed->getEntries()->first(); - $this->setToken(self::$token); + $this->setToken(self::$DI['oauth2-app-acc-user-not-admin']->getOauthToken()->getOauthToken()); $route = '/api/v1/feeds/entry/' . $created_entry->getId() . '/'; $this->evaluateMethodNotAllowedRoute($route, ['POST', 'PUT', 'DELETE']); @@ -1641,7 +1568,7 @@ abstract class ApiTestCase extends \PhraseanetWebTestCase $created_feed->setCollection(self::$DI['collection_no_access']); - $this->setToken(self::$adminToken); + $this->setToken(self::$DI['oauth2-app-acc-user']->getOauthToken()->getOauthToken()); $route = '/api/v1/feeds/entry/' . $created_entry->getId() . '/'; $this->evaluateMethodNotAllowedRoute($route, ['POST', 'PUT', 'DELETE']); @@ -1669,7 +1596,7 @@ abstract class ApiTestCase extends \PhraseanetWebTestCase self::$DI['app']['EM']->persist($created_entry); self::$DI['app']['EM']->flush(); - $this->setToken(self::$token); + $this->setToken(self::$DI['oauth2-app-acc-user-not-admin']->getOauthToken()->getOauthToken()); $route = '/api/v1/feeds/' . $created_feed->getId() . '/content/'; $this->evaluateMethodNotAllowedRoute($route, ['POST', 'PUT', 'DELETE']); @@ -1705,7 +1632,7 @@ abstract class ApiTestCase extends \PhraseanetWebTestCase public function testQuarantineList() { - $this->setToken(self::$token); + $this->setToken(self::$DI['oauth2-app-acc-user-not-admin']->getOauthToken()->getOauthToken()); $route = '/api/v1/quarantine/list/'; $quarantineItemId = self::$DI['lazaret_1']->getId(); @@ -1736,7 +1663,7 @@ abstract class ApiTestCase extends \PhraseanetWebTestCase public function testQuarantineContent() { - $this->setToken(self::$token); + $this->setToken(self::$DI['oauth2-app-acc-user-not-admin']->getOauthToken()->getOauthToken()); $quarantineItemId = self::$DI['lazaret_1']->getId(); $route = '/api/v1/quarantine/item/' . $quarantineItemId . '/'; @@ -1781,7 +1708,7 @@ abstract class ApiTestCase extends \PhraseanetWebTestCase public function testRouteMe() { - $this->setToken(self::$token); + $this->setToken(self::$DI['oauth2-app-acc-user-not-admin']->getOauthToken()->getOauthToken()); $route = '/api/v1/me/'; diff --git a/tests/Alchemy/Tests/Phrasea/Controller/Api/OAuth2Test.php b/tests/Alchemy/Tests/Phrasea/Controller/Api/OAuth2Test.php index dfb7d0ce76..f074e48d30 100644 --- a/tests/Alchemy/Tests/Phrasea/Controller/Api/OAuth2Test.php +++ b/tests/Alchemy/Tests/Phrasea/Controller/Api/OAuth2Test.php @@ -6,20 +6,8 @@ use Alchemy\Phrasea\Core\PhraseaEvents; use Alchemy\Phrasea\Authentication\Context; use Alchemy\Phrasea\Model\Entities\ApiApplication; -/** - * Test oauthv2 flow based on ietf authv2 spec - * @link http://tools.ietf.org/html/draft-ietf-oauth-v2-18 - */ class OAuth2Test extends \PhraseanetAuthenticatedWebTestCase { - /** - * - * @var ApiApplication - */ - public static $account_id; - public static $account; - public $oauth; - protected $client; protected $queryParameters; public function setUp() @@ -33,30 +21,22 @@ class OAuth2Test extends \PhraseanetAuthenticatedWebTestCase $this->queryParameters = [ "response_type" => "code", "client_id" => self::$DI['oauth2-app-user']->getClientId(), - "redirect_uri" => self::$DI['oauth2-app-user']->getRedirectId(), + "redirect_uri" => self::$DI['oauth2-app-user']->getRedirectUri(), "scope" => "", "state" => "valueTest" ]; } - public static function tearDownAfterClass() - { - self::$account_id = self::$account = null; - parent::tearDownAfterClass(); - } - - public static function deleteInsertedRow(\appbox $appbox, ApiApplication $application) - { - self::$DI['app']['manipulator.api-application']->delete($application); - } - /** * @dataProvider provideEventNames */ public function testThatEventsAreTriggered($revoked, $method, $eventName, $className) { - $acc = self::getAccount(); - $acc->set_revoked($revoked); // revoked to show form + if ($revoked) { + self::$DI['app']['manipulator.api-account']->revokeAccess(self::$DI['oauth2-app-acc-user']); + } else { + self::$DI['app']['manipulator.api-account']->authorizeAccess(self::$DI['oauth2-app-acc-user']); + } $preEvent = 0; self::$DI['app']['dispatcher']->addListener($eventName, function ($event) use (&$preEvent, $className) { @@ -80,24 +60,6 @@ class OAuth2Test extends \PhraseanetAuthenticatedWebTestCase ]; } - public static function getApp($rowId) - { - $sql = "SELECT * FROM api_applications WHERE application_id = :app_id"; - $t = [":app_id" => $rowId]; - $conn = self::$DI['app']['phraseanet.appbox']->get_connection(); - $stmt = $conn->prepare($sql); - $stmt->execute($t); - $result = $stmt->fetch(\PDO::FETCH_ASSOC); - $stmt->closeCursor(); - - return $result; - } - - public static function getAccount() - { - return self::$DI['app']['repo.api-accounts']->findByUserAndApplication(self::$DI['user'], self::$DI['oauth2-app-user']); - } - public function setQueryParameters($parameter, $value) { $this->queryParameters[$parameter] = $value; @@ -105,16 +67,17 @@ class OAuth2Test extends \PhraseanetAuthenticatedWebTestCase public function unsetQueryParameter($parameter) { - if (isset($this->queryParameters[$parameter])) + if (isset($this->queryParameters[$parameter])) { unset($this->queryParameters[$parameter]); + } } public function testAuthorizeRedirect() { //session off - $apps = self::$DI['app']['repo.api-application']->findAuthorizedAppsByUser(self::$DI['user']); + $apps = self::$DI['app']['repo.api-applications']->findAuthorizedAppsByUser(self::$DI['user']); foreach ($apps as $app) { - if ($app->get_client_id() === self::$DI['oauth2-app-user']->getClientId()) { + if ($app->getClientId() === self::$DI['oauth2-app-user']->getClientId()) { self::$DI['client']->followRedirects(); } } @@ -122,9 +85,7 @@ class OAuth2Test extends \PhraseanetAuthenticatedWebTestCase public function testAuthorize() { - $acc = self::getAccount(); - $acc->set_revoked(true); // revoked to show form - + self::$DI['app']['manipulator.api-account']->revokeAccess(self::$DI['oauth2-app-acc-user']); self::$DI['client']->request('GET', '/api/oauthv2/authorize', $this->queryParameters); $this->assertTrue(self::$DI['client']->getResponse()->isSuccessful()); $this->assertRegExp("/" . self::$DI['oauth2-app-user']->getCLientId() . "/", self::$DI['client']->getResponse()->getContent()); @@ -139,7 +100,6 @@ class OAuth2Test extends \PhraseanetAuthenticatedWebTestCase $this->setQueryParameters('grant_type', 'authorization_code'); $this->setQueryParameters('code', '12345678918'); self::$DI['client']->request('POST', '/api/oauthv2/token', $this->queryParameters); - $this->assertEquals(400, self::$DI['client']->getResponse()->getStatusCode()); } } diff --git a/tests/Alchemy/Tests/Phrasea/Controller/Root/AccountTest.php b/tests/Alchemy/Tests/Phrasea/Controller/Root/AccountTest.php index f5d4edb5f9..bca05c83ce 100644 --- a/tests/Alchemy/Tests/Phrasea/Controller/Root/AccountTest.php +++ b/tests/Alchemy/Tests/Phrasea/Controller/Root/AccountTest.php @@ -355,7 +355,6 @@ class AccountTest extends \PhraseanetAuthenticatedWebTestCase public function testAUthorizedAppGrantAccessBadRequest() { self::$DI['client']->request('GET', '/account/security/application/3/grant/'); - $this->assertBadResponse(self::$DI['client']->getResponse()); } @@ -384,7 +383,6 @@ class AccountTest extends \PhraseanetAuthenticatedWebTestCase ]); $response = self::$DI['client']->getResponse(); - $this->assertTrue($response->isOk()); $json = json_decode($response->getContent()); $this->assertInstanceOf('StdClass', $json); diff --git a/tests/Alchemy/Tests/Phrasea/Controller/Root/DevelopersTest.php b/tests/Alchemy/Tests/Phrasea/Controller/Root/DevelopersTest.php index d65a1bfcd0..967bb21d54 100644 --- a/tests/Alchemy/Tests/Phrasea/Controller/Root/DevelopersTest.php +++ b/tests/Alchemy/Tests/Phrasea/Controller/Root/DevelopersTest.php @@ -78,7 +78,7 @@ class DevelopersTest extends \PhraseanetAuthenticatedWebTestCase /** * @cover \Alchemy\Phrasea\Controller\Root\Developers::getApp */ - public function testGetUnknowApp() + public function testGetUnknownApp() { self::$DI['client']->request('GET', '/developers/application/0/'); @@ -91,7 +91,7 @@ class DevelopersTest extends \PhraseanetAuthenticatedWebTestCase public function testGetApp() { $oauthApp = self::$DI['oauth2-app-user']; - self::$DI['client']->request('GET', '/developers/application/' . $oauthApp->get_id() . '/'); + self::$DI['client']->request('GET', '/developers/application/' . $oauthApp->getId() . '/'); $this->assertTrue(self::$DI['client']->getResponse()->isOk()); } @@ -124,14 +124,15 @@ class DevelopersTest extends \PhraseanetAuthenticatedWebTestCase { $oauthApp = self::$DI['app']['manipulator.api-application']->create( 'test app', - '', + ApiApplication::DESKTOP_TYPE, '', 'http://phraseanet.com/' ); - $this->XMLHTTPRequest('DELETE', '/developers/application/' . $oauthApp->getId() . '/'); + $id = $oauthApp->getId(); + $this->XMLHTTPRequest('DELETE', '/developers/application/' . $id . '/'); $this->assertTrue(self::$DI['client']->getResponse()->isOk()); - $this->assertNull(self::$DI['app']['repo.api-application']->find($oauthApp->getId())); + $this->assertNull(self::$DI['app']['repo.api-applications']->find($id)); } /** @@ -164,7 +165,7 @@ class DevelopersTest extends \PhraseanetAuthenticatedWebTestCase public function testRenewAppCallbackError2() { $oauthApp = self::$DI['oauth2-app-user']; - $this->XMLHTTPRequest('POST', '/developers/application/'.$oauthApp->get_id().'/callback/'); + $this->XMLHTTPRequest('POST', '/developers/application/'.$oauthApp->getId().'/callback/'); $this->assertTrue(self::$DI['client']->getResponse()->isOk()); $content = json_decode(self::$DI['client']->getResponse()->getContent()); $this->assertFalse($content->success); @@ -177,15 +178,15 @@ class DevelopersTest extends \PhraseanetAuthenticatedWebTestCase { $oauthApp = self::$DI['oauth2-app-user']; - $this->XMLHTTPRequest('POST', '/developers/application/' . $oauthApp->get_id() . '/callback/', [ - 'callback' => 'my.callback.com' - ]); + $this->XMLHTTPRequest('POST', '/developers/application/' . $oauthApp->getId() . '/callback/', [ + 'callback' => 'http://my.callback.com' + ]); $this->assertTrue(self::$DI['client']->getResponse()->isOk()); $content = json_decode(self::$DI['client']->getResponse()->getContent()); $this->assertTrue($content->success); - $oauthApp = self::$DI['app']['repo.api-application']->find($oauthApp->getId()); - $this->assertEquals('my.callback.com', $oauthApp->getRedirectUri()); + $oauthApp = self::$DI['app']['repo.api-applications']->find($oauthApp->getId()); + $this->assertEquals('http://my.callback.com', $oauthApp->getRedirectUri()); } /** @@ -205,12 +206,11 @@ class DevelopersTest extends \PhraseanetAuthenticatedWebTestCase { $this->XMLHTTPRequest('POST', '/developers/application/0/access_token/', [ 'callback' => 'my.callback.com' - ]); + ]); $this->assertTrue(self::$DI['client']->getResponse()->isOk()); $content = json_decode(self::$DI['client']->getResponse()->getContent()); $this->assertFalse($content->success); - $this->assertNull($content->token); } /** @@ -220,7 +220,7 @@ class DevelopersTest extends \PhraseanetAuthenticatedWebTestCase { $oauthApp = self::$DI['oauth2-app-user']; - $this->XMLHTTPRequest('POST', '/developers/application/' . $oauthApp->get_id() . '/access_token/'); + $this->XMLHTTPRequest('POST', '/developers/application/' . $oauthApp->getId() . '/access_token/'); $this->assertTrue(self::$DI['client']->getResponse()->isOk()); $content = json_decode(self::$DI['client']->getResponse()->getContent()); @@ -259,14 +259,14 @@ class DevelopersTest extends \PhraseanetAuthenticatedWebTestCase { $oauthApp = self::$DI['oauth2-app-user']; - $this->XMLHTTPRequest('POST', '/developers/application/' . $oauthApp->get_id() . '/authorize_grant_password/', [ + $this->XMLHTTPRequest('POST', '/developers/application/' . $oauthApp->getId() . '/authorize_grant_password/', [ 'grant' => '1' ]); $this->assertTrue(self::$DI['client']->getResponse()->isOk()); $content = json_decode(self::$DI['client']->getResponse()->getContent()); $this->assertTrue($content->success); - $oauthApp = self::$DI['app']['repo.api-application']->find($oauthApp->getId()); + $oauthApp = self::$DI['app']['repo.api-applications']->find($oauthApp->getId()); $this->assertTrue($oauthApp->isPasswordGranted()); } } diff --git a/tests/classes/PhraseanetTestCase.php b/tests/classes/PhraseanetTestCase.php index ac85c41c5e..cf8a0d0c85 100644 --- a/tests/classes/PhraseanetTestCase.php +++ b/tests/classes/PhraseanetTestCase.php @@ -199,11 +199,19 @@ abstract class PhraseanetTestCase extends WebTestCase }); self::$DI['oauth2-app-user'] = self::$DI->share(function ($DI) { - return new $DI['app']['repo.api-applications']->find(self::$fixtureIds['oauth']['user']); + return $DI['app']['repo.api-applications']->find(self::$fixtureIds['oauth']['user']); }); self::$DI['oauth2-app-user-not-admin'] = self::$DI->share(function ($DI) { - return new $DI['app']['repo.api-applications']->find(self::$fixtureIds['oauth']['user-not-admin']); + return $DI['app']['repo.api-applications']->find(self::$fixtureIds['oauth']['user-not-admin']); + }); + + self::$DI['oauth2-app-acc-user'] = self::$DI->share(function ($DI) { + return $DI['app']['repo.api-accounts']->find(self::$fixtureIds['oauth']['acc-user']); + }); + + self::$DI['oauth2-app-acc-user-not-admin'] = self::$DI->share(function ($DI) { + return $DI['app']['repo.api-accounts']->find(self::$fixtureIds['oauth']['acc-user-not-admin']); }); self::$DI['logger'] = self::$DI->share(function () { From 3ef081ec128348ee70f020f54626af01cdcd834b Mon Sep 17 00:00:00 2001 From: Nicolas Le Goff Date: Thu, 6 Mar 2014 17:46:24 +0100 Subject: [PATCH 075/112] Fix typo --- lib/classes/patch/370alpha3a.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/classes/patch/370alpha3a.php b/lib/classes/patch/370alpha3a.php index 3376a6eaaf..5a86489c55 100644 --- a/lib/classes/patch/370alpha3a.php +++ b/lib/classes/patch/370alpha3a.php @@ -59,7 +59,7 @@ class patch_370alpha3a extends patchAbstract */ public function apply(base $appbox, Application $app) { - if (null === $app['repo.api-application']->findByClientId(\API_OAuth2_Application_Navigator::CLIENT_ID)) { + if (null === $app['repo.api-applications']->findByClientId(\API_OAuth2_Application_Navigator::CLIENT_ID)) { $application = $app['manipulator.api-application']->create( \API_OAuth2_Application_Navigator::CLIENT_NAME, ApiApplication::DESKTOP_TYPE, From 5145848161c4b5bff8da2017eb9f1b1053a87b50 Mon Sep 17 00:00:00 2001 From: Nicolas Le Goff Date: Fri, 7 Mar 2014 14:20:15 +0100 Subject: [PATCH 076/112] Add Migration patch --- .../Setup/DoctrineMigrations/ApiMigration.php | 56 ++++++ lib/classes/patch/370alpha3a.php | 2 +- lib/classes/patch/390alpha17a.php | 187 ++++++++++++++++++ lib/conf.d/migrations.yml | 3 + 4 files changed, 247 insertions(+), 1 deletion(-) create mode 100644 lib/Alchemy/Phrasea/Setup/DoctrineMigrations/ApiMigration.php create mode 100644 lib/classes/patch/390alpha17a.php diff --git a/lib/Alchemy/Phrasea/Setup/DoctrineMigrations/ApiMigration.php b/lib/Alchemy/Phrasea/Setup/DoctrineMigrations/ApiMigration.php new file mode 100644 index 0000000000..1d1db8cd68 --- /dev/null +++ b/lib/Alchemy/Phrasea/Setup/DoctrineMigrations/ApiMigration.php @@ -0,0 +1,56 @@ +tableExists('ApiApplication'); + } + + public function doUpSql(Schema $schema) + { + $this->addSql("CREATE TABLE ApiLogs (id INT AUTO_INCREMENT NOT NULL, account_id INT NOT NULL, route VARCHAR(128) DEFAULT NULL, method VARCHAR(16) DEFAULT NULL, created DATETIME NOT NULL, status_code INT DEFAULT NULL, format VARCHAR(64) DEFAULT NULL, resource VARCHAR(64) DEFAULT NULL, general VARCHAR(64) DEFAULT NULL, aspect VARCHAR(64) DEFAULT NULL, action VARCHAR(64) DEFAULT NULL, error_code INT DEFAULT NULL, error_message LONGTEXT DEFAULT NULL, INDEX IDX_91E90F309B6B5FBA (account_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB"); + $this->addSql("CREATE TABLE ApiApplications (id INT AUTO_INCREMENT NOT NULL, creator_id INT DEFAULT NULL, type VARCHAR(128) NOT NULL, name VARCHAR(128) NOT NULL, description LONGTEXT NOT NULL, website VARCHAR(128) NOT NULL, created DATETIME NOT NULL, updated DATETIME NOT NULL, client_id VARCHAR(32) NOT NULL, client_secret VARCHAR(32) NOT NULL, nonce VARCHAR(64) NOT NULL, redirect_uri VARCHAR(128) NOT NULL, activated TINYINT(1) NOT NULL, grant_password TINYINT(1) NOT NULL, INDEX IDX_53F7BBE661220EA6 (creator_id), UNIQUE INDEX client_id (client_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB"); + $this->addSql("CREATE TABLE ApiOauthCodes (code VARCHAR(16) NOT NULL, account_id INT NOT NULL, redirect_uri VARCHAR(128) NOT NULL, expires DATETIME DEFAULT NULL, scope VARCHAR(128) DEFAULT NULL, created DATETIME NOT NULL, updated DATETIME NOT NULL, INDEX IDX_BE6B11809B6B5FBA (account_id), PRIMARY KEY(code)) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB"); + $this->addSql("CREATE TABLE ApiOauthRefreshTokens (refresh_token VARCHAR(128) NOT NULL, account_id INT NOT NULL, expires DATETIME NOT NULL, scope VARCHAR(128) DEFAULT NULL, created DATETIME NOT NULL, updated DATETIME NOT NULL, INDEX IDX_7DA42A5A9B6B5FBA (account_id), PRIMARY KEY(refresh_token)) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB"); + $this->addSql("CREATE TABLE ApiOauthTokens (oauth_token VARCHAR(32) NOT NULL, account_id INT NOT NULL, session_id INT DEFAULT NULL, expires DATETIME DEFAULT NULL, scope VARCHAR(128) DEFAULT NULL, created DATETIME NOT NULL, updated DATETIME NOT NULL, UNIQUE INDEX UNIQ_4FD469539B6B5FBA (account_id), INDEX session_id (session_id), PRIMARY KEY(oauth_token)) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB"); + $this->addSql("CREATE TABLE ApiAccounts (id INT AUTO_INCREMENT NOT NULL, user_id INT NOT NULL, application_id INT NOT NULL, oauth_token VARCHAR(32) DEFAULT NULL, revoked TINYINT(1) NOT NULL, api_version VARCHAR(16) NOT NULL, created DATETIME NOT NULL, INDEX IDX_2C54E637A76ED395 (user_id), INDEX IDX_2C54E6373E030ACD (application_id), UNIQUE INDEX UNIQ_2C54E637D8344B2A (oauth_token), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB"); + $this->addSql("ALTER TABLE ApiLogs ADD CONSTRAINT FK_91E90F309B6B5FBA FOREIGN KEY (account_id) REFERENCES ApiAccounts (id)"); + $this->addSql("ALTER TABLE ApiApplications ADD CONSTRAINT FK_53F7BBE661220EA6 FOREIGN KEY (creator_id) REFERENCES Users (id)"); + $this->addSql("ALTER TABLE ApiOauthCodes ADD CONSTRAINT FK_BE6B11809B6B5FBA FOREIGN KEY (account_id) REFERENCES ApiAccounts (id)"); + $this->addSql("ALTER TABLE ApiOauthRefreshTokens ADD CONSTRAINT FK_7DA42A5A9B6B5FBA FOREIGN KEY (account_id) REFERENCES ApiAccounts (id)"); + $this->addSql("ALTER TABLE ApiOauthTokens ADD CONSTRAINT FK_4FD469539B6B5FBA FOREIGN KEY (account_id) REFERENCES ApiAccounts (id)"); + $this->addSql("ALTER TABLE ApiAccounts ADD CONSTRAINT FK_2C54E637A76ED395 FOREIGN KEY (user_id) REFERENCES Users (id)"); + $this->addSql("ALTER TABLE ApiAccounts ADD CONSTRAINT FK_2C54E6373E030ACD FOREIGN KEY (application_id) REFERENCES ApiApplications (id)"); + $this->addSql("ALTER TABLE ApiAccounts ADD CONSTRAINT FK_2C54E637D8344B2A FOREIGN KEY (oauth_token) REFERENCES ApiOauthTokens (oauth_token)"); + } + + public function doDownSql(Schema $schema) + { + $this->addSql("ALTER TABLE ApiAccounts DROP FOREIGN KEY FK_2C54E6373E030ACD"); + $this->addSql("ALTER TABLE ApiAccounts DROP FOREIGN KEY FK_2C54E637D8344B2A"); + $this->addSql("ALTER TABLE ApiLogs DROP FOREIGN KEY FK_91E90F309B6B5FBA"); + $this->addSql("ALTER TABLE ApiOauthCodes DROP FOREIGN KEY FK_BE6B11809B6B5FBA"); + $this->addSql("ALTER TABLE ApiOauthRefreshTokens DROP FOREIGN KEY FK_7DA42A5A9B6B5FBA"); + $this->addSql("ALTER TABLE ApiOauthTokens DROP FOREIGN KEY FK_4FD469539B6B5FBA"); + $this->addSql("DROP TABLE ApiLogs"); + $this->addSql("DROP TABLE ApiApplications"); + $this->addSql("DROP TABLE ApiOauthCodes"); + $this->addSql("DROP TABLE ApiOauthRefreshTokens"); + $this->addSql("DROP TABLE ApiOauthTokens"); + $this->addSql("DROP TABLE ApiAccounts"); + } +} diff --git a/lib/classes/patch/370alpha3a.php b/lib/classes/patch/370alpha3a.php index 5a86489c55..094e797f4b 100644 --- a/lib/classes/patch/370alpha3a.php +++ b/lib/classes/patch/370alpha3a.php @@ -43,7 +43,7 @@ class patch_370alpha3a extends patchAbstract */ public function getDoctrineMigrations() { - return []; + return ['api']; } /** diff --git a/lib/classes/patch/390alpha17a.php b/lib/classes/patch/390alpha17a.php new file mode 100644 index 0000000000..8c2c3d5185 --- /dev/null +++ b/lib/classes/patch/390alpha17a.php @@ -0,0 +1,187 @@ +release; + } + + /** + * {@inheritdoc} + */ + public function require_all_upgrades() + { + return false; + } + + /** + * {@inheritdoc} + */ + public function concern() + { + return $this->concern; + } + + /** + * {@inheritdoc} + */ + public function getDoctrineMigrations() + { + return ['api']; + } + + /** + * {@inheritdoc} + */ + public function apply(base $appbox, Application $app) + { + $this->fillApplicationTable($app['EM']); + $this->fillAccountTable($app['EM']); + $this->fillLogTable($app['EM']); + $this->fillCodeTable($app['EM']); + $this->fillRefreshTokenTable($app['EM']); + $this->fillOauthTokenTable($app['EM']); + } + + private function fillApplicationTable(EntityManager $em) + { + $em->getConnection()->executeUpdate( + 'INSERT INTO ApiApplications + ( + id, `type`, `name`, description, website + created, updated, client_id, client_secret, nonce + redirect_uri, activated, grant_password, creator_id + + ) + ( + SELECT + application_id, `type`, `name`, description, website, + created_on, last_modified, client_id, client_secret, nonce, + redirect_uri, activated, grant_password, creator + FROM api_applications + INNER JOIN Users ON (Users.id = api_accounts.usr_id) + )' + ); + } + + private function fillAccountTable(EntityManager $em) + { + $em->getConnection()->executeUpdate( + 'INSERT INTO ApiAccounts + ( + id, user_id, revoked + api_version, created, application_id + + ) + ( + SELECT + api_account_id, usr_id, revoked, + api_version, created, application_id + FROM api_accounts + INNER JOIN Users ON (Users.id = api_accounts.usr_id) + INNER JOIN api_applications ON (api_accounts.application_id = api_applications.application_id) + )' + ); + } + + private function fillLogTable(EntityManager $em) + { + $em->getConnection()->executeUpdate( + 'INSERT INTO ApiLogs + ( + id, account_id, route, error_message + created, status_code, format, resource, + general, aspect, `action`, error_code, + + ) + ( + SELECT + api_log_id, api_account_id, api_log_route, api_log_error_message + api_log_date, api_log_status_code, api_log_format, api_log_resource, + api_log_general, api_log_aspect, api_log_action, api_log_error_code + FROM api_log + INNER JOIN api_accounts ON (api_accounts.api_account_id = api_log.api_account_id) + )' + ); + } + + private function fillCodeTable(EntityManager $em) + { + $em->getConnection()->executeUpdate( + 'INSERT INTO ApiOauthCodes + ( + code, account_id, redirect_uri, expires + scope, created, updated + + ) + ( + SELECT + code, api_account_id, redirect_uri, expires + scope, NOW(), NOW() + FROM api_oauth_codes + INNER JOIN api_accounts ON (api_accounts.api_account_id = api_oauth_codes.api_account_id) + )' + ); + } + + private function fillRefreshTokenTable(EntityManager $em) + { + $em->getConnection()->executeUpdate( + 'INSERT INTO ApiOauthRefreshTokens + ( + refresh_token, account_id, expires + scope, created, updated + + ) + ( + SELECT + refresh_token, api_account_id, expires + scope, NOW(), NOW() + FROM api_oauth_refresh_tokens + INNER JOIN api_accounts ON (api_accounts.api_account_id = api_oauth_refresh_tokens.api_account_id) + )' + ); + } + + + private function fillOauthTokenTable(EntityManager $em) + { + $em->getConnection()->executeUpdate( + 'INSERT INTO ApiOauthTokens + ( + oauth_token, account_id, session_id, expires + scope, created, updated + + ) + ( + SELECT + oauth_token, api_account_id, session_id, expires + scope, NOW(), NOW() + FROM api_oauth_tokens + INNER JOIN api_accounts ON (api_accounts.api_account_id = api_oauth_tokens.api_account_id) + )' + ); + } +} diff --git a/lib/conf.d/migrations.yml b/lib/conf.d/migrations.yml index 2cc0b1cde8..5d58335458 100644 --- a/lib/conf.d/migrations.yml +++ b/lib/conf.d/migrations.yml @@ -60,6 +60,9 @@ migrations: migration19: version: token class: Alchemy\Phrasea\Setup\DoctrineMigrations\TokenMigration + migration20: + version: api + class: Alchemy\Phrasea\Setup\DoctrineMigrations\ApiMigration migration21: version: aggregate-token class: Alchemy\Phrasea\Setup\DoctrineMigrations\AggregateTokenMigration From 2c19ec546d730dbc6a25daf8ca050d4891d03890 Mon Sep 17 00:00:00 2001 From: Nicolas Le Goff Date: Fri, 7 Mar 2014 14:48:05 +0100 Subject: [PATCH 077/112] Add providers tests --- ....php => ConvertersServiceProviderTest.php} | 13 +++++--- .../ManipulatorServiceProviderTest.php | 30 +++++++++++++++++++ .../RepositoriesServiceProviderTest.php | 6 ++++ 3 files changed, 45 insertions(+), 4 deletions(-) rename tests/Alchemy/Tests/Phrasea/Core/Provider/{ConvertersServiceProvider.php => ConvertersServiceProviderTest.php} (53%) diff --git a/tests/Alchemy/Tests/Phrasea/Core/Provider/ConvertersServiceProvider.php b/tests/Alchemy/Tests/Phrasea/Core/Provider/ConvertersServiceProviderTest.php similarity index 53% rename from tests/Alchemy/Tests/Phrasea/Core/Provider/ConvertersServiceProvider.php rename to tests/Alchemy/Tests/Phrasea/Core/Provider/ConvertersServiceProviderTest.php index 2bd05318bf..002986f665 100644 --- a/tests/Alchemy/Tests/Phrasea/Core/Provider/ConvertersServiceProvider.php +++ b/tests/Alchemy/Tests/Phrasea/Core/Provider/ConvertersServiceProviderTest.php @@ -2,7 +2,7 @@ namespace Alchemy\Tests\Phrasea\Core\Provider; -class ConvertersServiceProvider extends ServiceProviderTestCase +class ConvertersServiceProviderTest extends ServiceProviderTestCase { public function provideServiceDescription() { @@ -10,17 +10,22 @@ class ConvertersServiceProvider extends ServiceProviderTestCase [ 'Alchemy\Phrasea\Core\Provider\ConvertersServiceProvider', 'converter.task', - 'Alchemy\Phrasea\Controller\Converter\TaskConverter' + 'Alchemy\Phrasea\Model\Converter\TaskConverter' ], [ 'Alchemy\Phrasea\Core\Provider\ConvertersServiceProvider', 'converter.basket', - 'Alchemy\Phrasea\Controller\Converter\BasketConverter' + 'Alchemy\Phrasea\Model\Converter\BasketConverter' ], [ 'Alchemy\Phrasea\Core\Provider\ConvertersServiceProvider', 'converter.token', - 'Alchemy\Phrasea\Controller\Converter\TokenConverter' + 'Alchemy\Phrasea\Model\Converter\TokenConverter' + ], + [ + 'Alchemy\Phrasea\Core\Provider\ConvertersServiceProvider', + 'converter.api-application', + 'Alchemy\Phrasea\Model\Converter\ApiApplicationConverter' ], ]; } diff --git a/tests/Alchemy/Tests/Phrasea/Core/Provider/ManipulatorServiceProviderTest.php b/tests/Alchemy/Tests/Phrasea/Core/Provider/ManipulatorServiceProviderTest.php index 6ee7d04b2c..f7777daaa7 100644 --- a/tests/Alchemy/Tests/Phrasea/Core/Provider/ManipulatorServiceProviderTest.php +++ b/tests/Alchemy/Tests/Phrasea/Core/Provider/ManipulatorServiceProviderTest.php @@ -27,6 +27,36 @@ class ManipulatorServiceProviderTest extends ServiceProviderTestCase 'manipulator.token', 'Alchemy\Phrasea\Model\Manipulator\TokenManipulator' ], + [ + 'Alchemy\Phrasea\Core\Provider\ManipulatorServiceProvider', + 'manipulator.api-application', + 'Alchemy\Phrasea\Model\Manipulator\ApiApplicationManipulator' + ], + [ + 'Alchemy\Phrasea\Core\Provider\ManipulatorServiceProvider', + 'manipulator.api-account', + 'Alchemy\Phrasea\Model\Manipulator\ApiAccountManipulator' + ], + [ + 'Alchemy\Phrasea\Core\Provider\ManipulatorServiceProvider', + 'manipulator.api-log', + 'Alchemy\Phrasea\Model\Manipulator\ApiLogManipulator' + ], + [ + 'Alchemy\Phrasea\Core\Provider\ManipulatorServiceProvider', + 'manipulator.api-oauth-token', + 'Alchemy\Phrasea\Model\Manipulator\ApiOauthTokenManipulator' + ], + [ + 'Alchemy\Phrasea\Core\Provider\ManipulatorServiceProvider', + 'manipulator.api-oauth-code', + 'Alchemy\Phrasea\Model\Manipulator\ApiOauthCodeManipulator' + ], + [ + 'Alchemy\Phrasea\Core\Provider\ManipulatorServiceProvider', + 'manipulator.api-oauth-refresh-token', + 'Alchemy\Phrasea\Model\Manipulator\ApiOauthRefreshTokenManipulator' + ], ]; } } diff --git a/tests/Alchemy/Tests/Phrasea/Core/Provider/RepositoriesServiceProviderTest.php b/tests/Alchemy/Tests/Phrasea/Core/Provider/RepositoriesServiceProviderTest.php index 65eb629cd2..7ccc478279 100644 --- a/tests/Alchemy/Tests/Phrasea/Core/Provider/RepositoriesServiceProviderTest.php +++ b/tests/Alchemy/Tests/Phrasea/Core/Provider/RepositoriesServiceProviderTest.php @@ -33,6 +33,12 @@ class RepositoriesServiceProviderTest extends ServiceProviderTestCase ['Alchemy\Phrasea\Core\Provider\RepositoriesServiceProvider', 'repo.user-queries', 'Alchemy\Phrasea\Model\Repositories\UserQueryRepository'], ['Alchemy\Phrasea\Core\Provider\RepositoriesServiceProvider', 'repo.tokens', 'Alchemy\Phrasea\Model\Repositories\TokenRepository'], ['Alchemy\Phrasea\Core\Provider\RepositoriesServiceProvider', 'repo.presets', 'Alchemy\Phrasea\Model\Repositories\PresetRepository'], + ['Alchemy\Phrasea\Core\Provider\RepositoriesServiceProvider', 'repo.api-applications', 'Alchemy\Phrasea\Model\Repositories\ApiApplicationRepository'], + ['Alchemy\Phrasea\Core\Provider\RepositoriesServiceProvider', 'repo.api-accounts', 'Alchemy\Phrasea\Model\Repositories\ApiAccountRepository'], + ['Alchemy\Phrasea\Core\Provider\RepositoriesServiceProvider', 'repo.api-logs', 'Alchemy\Phrasea\Model\Repositories\ApiLogRepository'], + ['Alchemy\Phrasea\Core\Provider\RepositoriesServiceProvider', 'repo.api-oauth-tokens', 'Alchemy\Phrasea\Model\Repositories\ApiOauthTokenRepository'], + ['Alchemy\Phrasea\Core\Provider\RepositoriesServiceProvider', 'repo.api-oauth-codes', 'Alchemy\Phrasea\Model\Repositories\ApiOauthCodeRepository'], + ['Alchemy\Phrasea\Core\Provider\RepositoriesServiceProvider', 'repo.api-oauth-refresh-tokens', 'Alchemy\Phrasea\Model\Repositories\ApiOauthRefreshTokenRepository'], ]; } } From bf5b02a583b410f916e1dbc3aa2f77015eac667e Mon Sep 17 00:00:00 2001 From: Nicolas Le Goff Date: Fri, 7 Mar 2014 14:53:22 +0100 Subject: [PATCH 078/112] Sets api tokens fields length to 128 for migration purpose --- lib/Alchemy/Phrasea/Model/Entities/ApiOauthCode.php | 2 +- lib/Alchemy/Phrasea/Model/Entities/ApiOauthToken.php | 2 +- lib/Alchemy/Phrasea/Setup/DoctrineMigrations/ApiMigration.php | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/Alchemy/Phrasea/Model/Entities/ApiOauthCode.php b/lib/Alchemy/Phrasea/Model/Entities/ApiOauthCode.php index 9e1cc7d4fa..4e9f7d2f92 100644 --- a/lib/Alchemy/Phrasea/Model/Entities/ApiOauthCode.php +++ b/lib/Alchemy/Phrasea/Model/Entities/ApiOauthCode.php @@ -14,7 +14,7 @@ class ApiOauthCode /** * @var string * - * @ORM\Column(name="code", type="string", length=16, nullable=false) + * @ORM\Column(name="code", type="string", length=128, nullable=false) * @ORM\Id */ private $code; diff --git a/lib/Alchemy/Phrasea/Model/Entities/ApiOauthToken.php b/lib/Alchemy/Phrasea/Model/Entities/ApiOauthToken.php index 93e1aa32b6..5a2da03271 100644 --- a/lib/Alchemy/Phrasea/Model/Entities/ApiOauthToken.php +++ b/lib/Alchemy/Phrasea/Model/Entities/ApiOauthToken.php @@ -14,7 +14,7 @@ class ApiOauthToken /** * @var string * - * @ORM\Column(name="oauth_token", type="string", length=32, nullable=false) + * @ORM\Column(name="oauth_token", type="string", length=128, nullable=false) * @ORM\Id */ private $oauthToken; diff --git a/lib/Alchemy/Phrasea/Setup/DoctrineMigrations/ApiMigration.php b/lib/Alchemy/Phrasea/Setup/DoctrineMigrations/ApiMigration.php index 1d1db8cd68..737fdd6c1c 100644 --- a/lib/Alchemy/Phrasea/Setup/DoctrineMigrations/ApiMigration.php +++ b/lib/Alchemy/Phrasea/Setup/DoctrineMigrations/ApiMigration.php @@ -24,9 +24,9 @@ class ApiMigration extends AbstractMigration { $this->addSql("CREATE TABLE ApiLogs (id INT AUTO_INCREMENT NOT NULL, account_id INT NOT NULL, route VARCHAR(128) DEFAULT NULL, method VARCHAR(16) DEFAULT NULL, created DATETIME NOT NULL, status_code INT DEFAULT NULL, format VARCHAR(64) DEFAULT NULL, resource VARCHAR(64) DEFAULT NULL, general VARCHAR(64) DEFAULT NULL, aspect VARCHAR(64) DEFAULT NULL, action VARCHAR(64) DEFAULT NULL, error_code INT DEFAULT NULL, error_message LONGTEXT DEFAULT NULL, INDEX IDX_91E90F309B6B5FBA (account_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB"); $this->addSql("CREATE TABLE ApiApplications (id INT AUTO_INCREMENT NOT NULL, creator_id INT DEFAULT NULL, type VARCHAR(128) NOT NULL, name VARCHAR(128) NOT NULL, description LONGTEXT NOT NULL, website VARCHAR(128) NOT NULL, created DATETIME NOT NULL, updated DATETIME NOT NULL, client_id VARCHAR(32) NOT NULL, client_secret VARCHAR(32) NOT NULL, nonce VARCHAR(64) NOT NULL, redirect_uri VARCHAR(128) NOT NULL, activated TINYINT(1) NOT NULL, grant_password TINYINT(1) NOT NULL, INDEX IDX_53F7BBE661220EA6 (creator_id), UNIQUE INDEX client_id (client_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB"); - $this->addSql("CREATE TABLE ApiOauthCodes (code VARCHAR(16) NOT NULL, account_id INT NOT NULL, redirect_uri VARCHAR(128) NOT NULL, expires DATETIME DEFAULT NULL, scope VARCHAR(128) DEFAULT NULL, created DATETIME NOT NULL, updated DATETIME NOT NULL, INDEX IDX_BE6B11809B6B5FBA (account_id), PRIMARY KEY(code)) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB"); + $this->addSql("CREATE TABLE ApiOauthCodes (code VARCHAR(128) NOT NULL, account_id INT NOT NULL, redirect_uri VARCHAR(128) NOT NULL, expires DATETIME DEFAULT NULL, scope VARCHAR(128) DEFAULT NULL, created DATETIME NOT NULL, updated DATETIME NOT NULL, INDEX IDX_BE6B11809B6B5FBA (account_id), PRIMARY KEY(code)) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB"); $this->addSql("CREATE TABLE ApiOauthRefreshTokens (refresh_token VARCHAR(128) NOT NULL, account_id INT NOT NULL, expires DATETIME NOT NULL, scope VARCHAR(128) DEFAULT NULL, created DATETIME NOT NULL, updated DATETIME NOT NULL, INDEX IDX_7DA42A5A9B6B5FBA (account_id), PRIMARY KEY(refresh_token)) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB"); - $this->addSql("CREATE TABLE ApiOauthTokens (oauth_token VARCHAR(32) NOT NULL, account_id INT NOT NULL, session_id INT DEFAULT NULL, expires DATETIME DEFAULT NULL, scope VARCHAR(128) DEFAULT NULL, created DATETIME NOT NULL, updated DATETIME NOT NULL, UNIQUE INDEX UNIQ_4FD469539B6B5FBA (account_id), INDEX session_id (session_id), PRIMARY KEY(oauth_token)) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB"); + $this->addSql("CREATE TABLE ApiOauthTokens (oauth_token VARCHAR(128) NOT NULL, account_id INT NOT NULL, session_id INT DEFAULT NULL, expires DATETIME DEFAULT NULL, scope VARCHAR(128) DEFAULT NULL, created DATETIME NOT NULL, updated DATETIME NOT NULL, UNIQUE INDEX UNIQ_4FD469539B6B5FBA (account_id), INDEX session_id (session_id), PRIMARY KEY(oauth_token)) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB"); $this->addSql("CREATE TABLE ApiAccounts (id INT AUTO_INCREMENT NOT NULL, user_id INT NOT NULL, application_id INT NOT NULL, oauth_token VARCHAR(32) DEFAULT NULL, revoked TINYINT(1) NOT NULL, api_version VARCHAR(16) NOT NULL, created DATETIME NOT NULL, INDEX IDX_2C54E637A76ED395 (user_id), INDEX IDX_2C54E6373E030ACD (application_id), UNIQUE INDEX UNIQ_2C54E637D8344B2A (oauth_token), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB"); $this->addSql("ALTER TABLE ApiLogs ADD CONSTRAINT FK_91E90F309B6B5FBA FOREIGN KEY (account_id) REFERENCES ApiAccounts (id)"); $this->addSql("ALTER TABLE ApiApplications ADD CONSTRAINT FK_53F7BBE661220EA6 FOREIGN KEY (creator_id) REFERENCES Users (id)"); From a3e63f7cb2e951221219adaa673dfcd1d6cedd73 Mon Sep 17 00:00:00 2001 From: Nicolas Le Goff Date: Fri, 7 Mar 2014 15:40:26 +0100 Subject: [PATCH 079/112] Add ApiApplicationManipulator tests --- .../ApiApplicationManipulatorTest.php | 148 ++++++++++++++++++ 1 file changed, 148 insertions(+) create mode 100644 tests/Alchemy/Tests/Phrasea/Model/Manipulator/ApiApplicationManipulatorTest.php diff --git a/tests/Alchemy/Tests/Phrasea/Model/Manipulator/ApiApplicationManipulatorTest.php b/tests/Alchemy/Tests/Phrasea/Model/Manipulator/ApiApplicationManipulatorTest.php new file mode 100644 index 0000000000..167d6577e5 --- /dev/null +++ b/tests/Alchemy/Tests/Phrasea/Model/Manipulator/ApiApplicationManipulatorTest.php @@ -0,0 +1,148 @@ +findAll()); + $application = $manipulator->create( + 'desktop-app', + ApiApplication::DESKTOP_TYPE, + 'Desktop application description', + 'http://desktop-app-url.net' + ); + $this->assertGreaterThan($nbApps, count(self::$DI['app']['repo.api-applications']->findAll())); + $this->assertNotNull($application->getClientId()); + $this->assertNotNull($application->getClientSecret()); + $this->assertNotNull($application->getNonce()); + $this->assertEquals('desktop-app', $application->getName()); + $this->assertEquals(ApiApplication::DESKTOP_TYPE, $application->getType()); + $this->assertEquals('http://desktop-app-url.net', $application->getWebsite()); + $this->assertEquals(ApiApplication::NATIVE_APP_REDIRECT_URI, $application->getRedirectUri()); + } + + public function testCreateWebApplication() + { + $manipulator = new ApiApplicationManipulator(self::$DI['app']['EM'], self::$DI['app']['repo.api-applications'], self::$DI['app']['random.medium']); + $nbApps = count(self::$DI['app']['repo.api-applications']->findAll()); + $application = $manipulator->create( + 'web-app', + ApiApplication::WEB_TYPE, + 'Desktop application description', + 'http://web-app-url.net', + self::$DI['user'], + 'http://web-app-url.net/callback' + ); + + $this->assertGreaterThan($nbApps, count(self::$DI['app']['repo.api-applications']->findAll())); + $this->assertNotNull($application->getClientId()); + $this->assertNotNull($application->getClientSecret()); + $this->assertNotNull($application->getNonce()); + $this->assertEquals('web-app', $application->getName()); + $this->assertEquals(ApiApplication::WEB_TYPE, $application->getType()); + $this->assertEquals('http://web-app-url.net', $application->getWebsite()); + $this->assertEquals('http://web-app-url.net/callback', $application->getRedirectUri()); + } + + public function testDelete() + { + $manipulator = new ApiApplicationManipulator(self::$DI['app']['EM'], self::$DI['app']['repo.api-applications'], self::$DI['app']['random.medium']); + $application = $manipulator->create( + 'desktop-app', + ApiApplication::DESKTOP_TYPE, + 'Desktop application description', + 'http://desktop-app-url.net' + ); + $countBefore = count(self::$DI['app']['repo.api-applications']->findAll()); + /** + * @todo Link accounts and tokens to application and tests if everything is deleted + */ + $manipulator->delete($application); + $this->assertGreaterThan(count(self::$DI['app']['repo.api-applications']->findAll()), $countBefore); + } + + public function testUpdate() + { + $manipulator = new ApiApplicationManipulator(self::$DI['app']['EM'], self::$DI['app']['repo.api-applications'], self::$DI['app']['random.medium']); + $application = $manipulator->create( + 'desktop-app', + ApiApplication::DESKTOP_TYPE, + 'Desktop application description', + 'http://desktop-app-url.net' + ); + $application->setName('new-desktop-app'); + $manipulator->update($application); + $application = self::$DI['app']['repo.api-applications']->find($application->getId()); + $this->assertEquals('new-desktop-app', $application->getName()); + } + + public function testSetType() + { + $manipulator = new ApiApplicationManipulator(self::$DI['app']['EM'], self::$DI['app']['repo.api-applications'], self::$DI['app']['random.medium']); + $application = $manipulator->create( + 'desktop-app', + ApiApplication::DESKTOP_TYPE, + 'Desktop application description', + 'http://desktop-app-url.net' + ); + try { + $manipulator->setType($application, 'invalid-type'); + $this->fail('Invalid argument exception should be raised'); + } catch (InvalidArgumentException $e) { + + } + } + + public function testSetRedirectUri() + { + $manipulator = new ApiApplicationManipulator(self::$DI['app']['EM'], self::$DI['app']['repo.api-applications'], self::$DI['app']['random.medium']); + $application = $manipulator->create( + 'desktop-app', + ApiApplication::DESKTOP_TYPE, + 'Desktop application description', + 'http://desktop-app-url.net' + ); + + $manipulator->setRedirectUri($application, 'invalid-url.com'); + $this->assertEquals(ApiApplication::NATIVE_APP_REDIRECT_URI, $application->getRedirectUri()); + + $application = $manipulator->create( + 'web-app', + ApiApplication::WEB_TYPE, + 'Desktop application description', + 'http://web-app-url.net', + self::$DI['user'], + 'http://web-app-url.net/callback' + ); + try { + $manipulator->setWebsiteUrl($application, 'invalid-url.com'); + $this->fail('Invalid argument exception should be raised'); + } catch (InvalidArgumentException $e) { + + } + } + + public function testSetWebsiteUrl() + { + $manipulator = new ApiApplicationManipulator(self::$DI['app']['EM'], self::$DI['app']['repo.api-applications'], self::$DI['app']['random.medium']); + $application = $manipulator->create( + 'desktop-app', + ApiApplication::DESKTOP_TYPE, + 'Desktop application description', + 'http://desktop-app-url.net' + ); + try { + $manipulator->setWebsiteUrl($application, 'invalid-url.com'); + $this->fail('Invalid argument exception should be raised'); + } catch (InvalidArgumentException $e) { + + } + } +} From c00c10822ec1d43ac026b107c9f69ce42be82483 Mon Sep 17 00:00:00 2001 From: Nicolas Le Goff Date: Fri, 7 Mar 2014 15:59:20 +0100 Subject: [PATCH 080/112] Add ApiAccountManipulator tests --- .../Manipulator/ApiAccountManipulator.php | 2 +- .../Manipulator/ApiAccountManipulatorTest.php | 59 +++++++++++++++++++ 2 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 tests/Alchemy/Tests/Phrasea/Model/Manipulator/ApiAccountManipulatorTest.php diff --git a/lib/Alchemy/Phrasea/Model/Manipulator/ApiAccountManipulator.php b/lib/Alchemy/Phrasea/Model/Manipulator/ApiAccountManipulator.php index 399cac4847..8c03eefcf0 100644 --- a/lib/Alchemy/Phrasea/Model/Manipulator/ApiAccountManipulator.php +++ b/lib/Alchemy/Phrasea/Model/Manipulator/ApiAccountManipulator.php @@ -31,7 +31,7 @@ class ApiAccountManipulator implements ManipulatorInterface $this->repository = $repo; } - public function create(ApiApplication $application, User $user = null) + public function create(ApiApplication $application, User $user) { $account = new ApiAccount(); $account->setUser($user); diff --git a/tests/Alchemy/Tests/Phrasea/Model/Manipulator/ApiAccountManipulatorTest.php b/tests/Alchemy/Tests/Phrasea/Model/Manipulator/ApiAccountManipulatorTest.php new file mode 100644 index 0000000000..ae4dafb9af --- /dev/null +++ b/tests/Alchemy/Tests/Phrasea/Model/Manipulator/ApiAccountManipulatorTest.php @@ -0,0 +1,59 @@ +findAll()); + $account = $manipulator->create(self::$DI['oauth2-app-user'], self::$DI['user']); + $this->assertGreaterThan($nbApps, count(self::$DI['app']['repo.api-accounts']->findAll())); + $this->assertFalse($account->isRevoked()); + $this->assertEquals(V1::VERSION, $account->getApiVersion()); + $this->assertGreaterThan($nbApps, count(self::$DI['app']['repo.api-accounts']->findAll())); + } + + public function testDelete() + { + $manipulator = new ApiAccountManipulator(self::$DI['app']['EM'], self::$DI['app']['repo.api-accounts']); + $account = $manipulator->create(self::$DI['oauth2-app-user'], self::$DI['user']); + $countBefore = count(self::$DI['app']['repo.api-accounts']->findAll()); + /** + * @todo Link token and tests if token is deleted too + */ + $manipulator->delete($account); + $this->assertGreaterThan(count(self::$DI['app']['repo.api-accounts']->findAll()), $countBefore); + } + + public function testUpdate() + { + $manipulator = new ApiAccountManipulator(self::$DI['app']['EM'], self::$DI['app']['repo.api-accounts']); + $account = $manipulator->create(self::$DI['oauth2-app-user'], self::$DI['user']); + $account->setApiVersion(24); + $manipulator->update($account); + $account = self::$DI['app']['repo.api-accounts']->find($account->getId()); + $this->assertEquals(24, $account->getApiVersion()); + } + + public function testAuthorizeAccess() + { + $manipulator = new ApiAccountManipulator(self::$DI['app']['EM'], self::$DI['app']['repo.api-accounts']); + $account = $manipulator->create(self::$DI['oauth2-app-user'], self::$DI['user']); + $manipulator->authorizeAccess($account); + $this->assertFalse($account->isRevoked()); + } + + public function testRevokeAccess() + { + $manipulator = new ApiAccountManipulator(self::$DI['app']['EM'], self::$DI['app']['repo.api-accounts']); + $account = $manipulator->create(self::$DI['oauth2-app-user'], self::$DI['user']); + $manipulator->revokeAccess($account); + $this->assertTrue($account->isRevoked()); + } +} From 03d19262b6c3a0ffaa042f406697bd4842e60341 Mon Sep 17 00:00:00 2001 From: Nicolas Le Goff Date: Fri, 7 Mar 2014 16:04:01 +0100 Subject: [PATCH 081/112] Fix token generation --- .../Phrasea/Model/Manipulator/ApiOauthCodeManipulator.php | 2 +- .../Model/Manipulator/ApiOauthRefreshTokenManipulator.php | 2 +- .../Phrasea/Model/Manipulator/ApiOauthTokenManipulator.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/Alchemy/Phrasea/Model/Manipulator/ApiOauthCodeManipulator.php b/lib/Alchemy/Phrasea/Model/Manipulator/ApiOauthCodeManipulator.php index 43022679c6..e7d25b9707 100644 --- a/lib/Alchemy/Phrasea/Model/Manipulator/ApiOauthCodeManipulator.php +++ b/lib/Alchemy/Phrasea/Model/Manipulator/ApiOauthCodeManipulator.php @@ -71,7 +71,7 @@ class ApiOauthCodeManipulator implements ManipulatorInterface { do { $code = $this->randomGenerator->generateString(16, TokenManipulator::LETTERS_AND_NUMBERS); - } while (null !== $this->repository->find($code)); + } while (null === $this->repository->find($code)); return $code; } diff --git a/lib/Alchemy/Phrasea/Model/Manipulator/ApiOauthRefreshTokenManipulator.php b/lib/Alchemy/Phrasea/Model/Manipulator/ApiOauthRefreshTokenManipulator.php index 416888907a..a65e8d5b1b 100644 --- a/lib/Alchemy/Phrasea/Model/Manipulator/ApiOauthRefreshTokenManipulator.php +++ b/lib/Alchemy/Phrasea/Model/Manipulator/ApiOauthRefreshTokenManipulator.php @@ -70,7 +70,7 @@ class ApiOauthRefreshTokenManipulator implements ManipulatorInterface { do { $refreshToken = $this->randomGenerator->generateString(32, TokenManipulator::LETTERS_AND_NUMBERS); - } while (null !== $this->repository->find($refreshToken)); + } while (null === $this->repository->find($refreshToken)); return $refreshToken; } diff --git a/lib/Alchemy/Phrasea/Model/Manipulator/ApiOauthTokenManipulator.php b/lib/Alchemy/Phrasea/Model/Manipulator/ApiOauthTokenManipulator.php index eb9408772a..650f4a9c46 100644 --- a/lib/Alchemy/Phrasea/Model/Manipulator/ApiOauthTokenManipulator.php +++ b/lib/Alchemy/Phrasea/Model/Manipulator/ApiOauthTokenManipulator.php @@ -87,7 +87,7 @@ class ApiOauthTokenManipulator implements ManipulatorInterface { do { $token = $this->randomGenerator->generateString(32, TokenManipulator::LETTERS_AND_NUMBERS); - } while (null !== $this->repository->find($token)); + } while (null === $this->repository->find($token)); return $token; } From ce41455e45fef0f3c99d377376bf67d1b6011db2 Mon Sep 17 00:00:00 2001 From: Nicolas Le Goff Date: Fri, 7 Mar 2014 16:06:24 +0100 Subject: [PATCH 082/112] Update ORM's tables --- lib/classes/base.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/classes/base.php b/lib/classes/base.php index a5dcb70971..094c9c3075 100644 --- a/lib/classes/base.php +++ b/lib/classes/base.php @@ -251,6 +251,12 @@ abstract class base implements cache_cacheableInterface $ORMTables = [ 'AuthFailures', + 'ApiApplications', + 'ApiAccounts', + 'ApiLogs', + 'ApiOauthCodes', + 'ApiOauthRefreshTokens', + 'ApiOauthTokens', 'AggregateTokens', 'BasketElements', 'Baskets', From 6c087f5a40ac20e6963b8c80d2fb4d4f53574cd7 Mon Sep 17 00:00:00 2001 From: Nicolas Le Goff Date: Fri, 7 Mar 2014 16:08:14 +0100 Subject: [PATCH 083/112] Remove api tables --- lib/conf.d/bases_structure.xml | 561 --------------------------------- 1 file changed, 561 deletions(-) diff --git a/lib/conf.d/bases_structure.xml b/lib/conf.d/bases_structure.xml index ecbe7f6013..84ba2894f7 100644 --- a/lib/conf.d/bases_structure.xml +++ b/lib/conf.d/bases_structure.xml @@ -77,567 +77,6 @@ InnoDB - - - - - api_account_id - int(11) unsigned - - auto_increment - - - - - usr_id - int(11) unsigned - - - - - - - revoked - int(1) - - - - - - - api_version - char(16) - - - - - - - application_id - int(11) - - - - - - - created - datetime - - - - - - - - - PRIMARY - PRIMARY - - api_account_id - - - - usr_id - INDEX - - usr_id - - - - application_id - INDEX - - application_id - - - - InnoDB -
- - - - - - application_id - int(11) unsigned - - auto_increment - - - - - creator - int(11) unsigned - YES - - - - - - type - enum('web','desktop') - - - - - - - name - varchar(64) - - - - - - - description - text - - - - - - - website - varchar(120) - - - - - - - created_on - datetime - - - - - - - last_modified - datetime - - - - - - - client_id - char(128) - - - - - - - client_secret - char(128) - - - - - - - nonce - char(64) - - - - - - - redirect_uri - varchar(128) - - - - - - - activated - int(1) - - - - - - - grant_password - int(1) - - - - - - - - - PRIMARY - PRIMARY - - application_id - - - - creator - INDEX - - creator - - - - client_id - UNIQUE - - client_id - - - - - - null - null - desktop - phraseanet-navigator - - http://www.phraseanet.com - NOW() - NOW() - \alchemy\phraseanet\id\4f981093aebb66.06844599 - \alchemy\phraseanet\secret\4f9810d4b09799.51622662 - 5b6lIf - urn:ietf:wg:oauth:2.0:oob - 1 - 1 - - - null - null - desktop - office-plugin - - http://www.phraseanet.com - NOW() - NOW() - \alchemy\phraseanet\id\999585175b5fbb6e140efbdfea86c561 - \alchemy\phraseanet\secret\6d53d0bc74e6c8c1a325541f71da1ea5 - AfCF61 - urn:ietf:wg:oauth:2.0:oob - 1 - 1 - - - InnoDB -
- - - - - - api_log_id - int(11) unsigned - - auto_increment - - - - - api_account_id - int(11) unsigned - YES - - - - - - api_log_route - varchar(256) - YES - - - - - - api_log_date - datetime - YES - - - - - - api_log_status_code - int(11) unsigned - YES - - - - - - api_log_format - varchar(64) - YES - - - - - - api_log_resource - varchar(64) - YES - - - - - - api_log_general - varchar(64) - YES - - - - - - api_log_aspect - varchar(64) - YES - - - - - - api_log_action - varchar(64) - YES - - - - - - api_log_error_code - int(11) unsigned - YES - - - - - - api_log_error_message - varchar(256) - YES - - - - - - - - PRIMARY - PRIMARY - - api_log_id - - - - api_account_id - INDEX - - api_account_id - - - - InnoDB -
- - - - - - code - char(128) - - - - - - - api_account_id - int(11) unsigned - - - - - - - redirect_uri - varchar(256) - - - - - - - expires - DATETIME - YES - - - - - - scope - varchar(200) - YES - - - - - - - - PRIMARY - PRIMARY - - code - - - - api_account_id - INDEX - - api_account_id - - - - InnoDB -
- - - - - - oauth_token - char(128) - - - - - - - session_id - int(6) - YES - - - - - - api_account_id - int(11) - - - - - - - expires - DATETIME - YES - - - - - - scope - varchar(200) - YES - - - - - - - - PRIMARY - PRIMARY - - oauth_token - - - - api_account_id - INDEX - - api_account_id - - - - session_id - INDEX - - session_id - - - - InnoDB -
- - - - - - refresh_token - char(128) - - - - - - - api_account_id - int(11) - - - - - - - expires - DATETIME - - - - - - - scope - varchar(200) - YES - - - - - - - - PRIMARY - PRIMARY - - refresh_token - - - - api_account_id - INDEX - - api_account_id - - - - InnoDB -
- From d09cbbb8cf552b5572755c9a3a3637e0df1654d8 Mon Sep 17 00:00:00 2001 From: Nicolas Le Goff Date: Fri, 7 Mar 2014 18:09:13 +0100 Subject: [PATCH 084/112] Add ApiLog Manipulator tests --- lib/Alchemy/Phrasea/Model/Entities/ApiLog.php | 5 +- .../Model/Manipulator/ApiLogManipulator.php | 165 +++++++++++++----- .../Manipulator/ApiLogManipulatorTest.php | 99 +++++++++++ 3 files changed, 228 insertions(+), 41 deletions(-) create mode 100644 tests/Alchemy/Tests/Phrasea/Model/Manipulator/ApiLogManipulatorTest.php diff --git a/lib/Alchemy/Phrasea/Model/Entities/ApiLog.php b/lib/Alchemy/Phrasea/Model/Entities/ApiLog.php index 646d07754d..766af26370 100644 --- a/lib/Alchemy/Phrasea/Model/Entities/ApiLog.php +++ b/lib/Alchemy/Phrasea/Model/Entities/ApiLog.php @@ -12,9 +12,12 @@ use Gedmo\Mapping\Annotation as Gedmo; class ApiLog { const DATABOXES_RESOURCE = 'databoxes'; - const RECORDS_RESOURCE = 'record'; + const RECORDS_RESOURCE = 'records'; const BASKETS_RESOURCE = 'baskets'; const FEEDS_RESOURCE = 'feeds'; + const QUARANTINE_RESOURCE = 'quarantine'; + const STORIES_RESOURCE = 'stories'; + const MONITOR_RESOURCE = 'monitor'; /** * @ORM\Column(type="integer") diff --git a/lib/Alchemy/Phrasea/Model/Manipulator/ApiLogManipulator.php b/lib/Alchemy/Phrasea/Model/Manipulator/ApiLogManipulator.php index 87b8c8037e..40046edca4 100644 --- a/lib/Alchemy/Phrasea/Model/Manipulator/ApiLogManipulator.php +++ b/lib/Alchemy/Phrasea/Model/Manipulator/ApiLogManipulator.php @@ -77,54 +77,139 @@ class ApiLogManipulator implements ManipulatorInterface */ private function setDetails(ApiLog $log, Request $request, Response $response) { - $resource = $general = $aspect = $action = null; $chunks = explode('/', trim($request->getPathInfo(), '/')); if (false === $response->isOk() || sizeof($chunks) === 0) { return; } - $resource = $chunks[0]; - if (count($chunks) == 2 && (int) $chunks[1] == 0) { - $general = $chunks[1]; - } else { - switch ($resource) { - case ApiLog::DATABOXES_RESOURCE : - if ((int) $chunks[1] > 0 && count($chunks) == 3) { - $aspect = $chunks[2]; - } - break; - case ApiLog::RECORDS_RESOURCE : - if ((int) $chunks[1] > 0 && count($chunks) == 4) { - if (!isset($chunks[3])) { - $aspect = "record"; - } elseif (preg_match("/^set/", $chunks[3])) { - $action = $chunks[3]; - } else { - $aspect = $chunks[3]; - } - } - break; - case ApiLog::BASKETS_RESOURCE : - if ((int) $chunks[1] > 0 && count($chunks) == 3) { - if (preg_match("/^set/", $chunks[2]) || preg_match("/^delete/", $chunks[2])) { - $action = $chunks[2]; - } else { - $aspect = $chunks[2]; - } - } - break; - case ApiLog::FEEDS_RESOURCE : - if ((int) $chunks[1] > 0 && count($chunks) == 3) { - $aspect = $chunks[2]; - } - break; + switch ($chunks[0]) { + case ApiLog::DATABOXES_RESOURCE : + $this->hydrateDataboxes($log, $chunks); + break; + case ApiLog::RECORDS_RESOURCE : + $this->hydrateRecords($log, $chunks); + break; + case ApiLog::BASKETS_RESOURCE : + $this->hydrateBaskets($log, $chunks); + break; + case ApiLog::FEEDS_RESOURCE : + $this->hydrateFeeds($log, $chunks); + break; + case ApiLog::QUARANTINE_RESOURCE : + $this->hydrateQuarantine($log, $chunks); + break; + case ApiLog::STORIES_RESOURCE : + $this->hydrateStories($log, $chunks); + break; + case ApiLog::MONITOR_RESOURCE : + $this->hydrateMonitor($log, $chunks); + break; + } + } + + private function hydrateDataboxes(ApiLog $log, $chunks) + { + $log->setResource($chunks[0]); + $log->setGeneral($chunks[0]); + if (count($chunks) === 2) { + $log->setAction($chunks[1]); + } + if ((int) $chunks[1] > 0 && count($chunks) === 3) { + $log->setAspect($chunks[2]); + } + } + + private function hydrateRecords(ApiLog $log, $chunks) + { + $log->setResource($chunks[0]); + $log->setGeneral($chunks[0]); + if (count($chunks) === 2) { + $log->setAction($chunks[1]); + } + if (count($chunks) === 3 && (int) $chunks[1] > 0 && (int) $chunks[2] > 0) { + $log->setAction('get'); + } + if ((int) $chunks[1] > 0 && (int) $chunks[2] > 0 && count($chunks) == 4) { + if (preg_match("/^set/", $chunks[3])) { + $log->setAction($chunks[3]); + } else { + $log->setAspect($chunks[3]); } } + } - $log->setResource($resource); - $log->setGeneral($general); - $log->setAspect($aspect); - $log->setAction($action); + private function hydrateBaskets(ApiLog $log, $chunks) + { + $log->setResource($chunks[0]); + $log->setGeneral($chunks[0]); + if (count($chunks) === 2) { + $log->setAction($chunks[1]); + } + if ((int) $chunks[1] > 0 && count($chunks) == 3) { + if (preg_match("/^set/", $chunks[2]) || preg_match("/^delete/", $chunks[2])) { + $log->setAction($chunks[2]); + } else { + $log->setAspect($chunks[2]); + } + } + } + + private function hydrateFeeds(ApiLog $log, $chunks) + { + $log->setResource($chunks[0]); + $log->setGeneral($chunks[0]); + if (count($chunks) === 2) { + if (preg_match("/^content$/", $chunks[1])) { + $log->setAspect($chunks[1]); + } else { + $log->setAction($chunks[1]); + } + } + if (count($chunks) === 3) { + if ((int) $chunks[1] > 0) { + $log->setAspect($chunks[2]); + } + if (preg_match("/^entry$/", $chunks[1]) && (int) $chunks[2] > 0) { + $log->setAspect($chunks[1]); + } + } + } + + private function hydrateQuarantine(ApiLog $log, $chunks) + { + $log->setResource($chunks[0]); + $log->setGeneral($chunks[0]); + if (count($chunks) === 2) { + $log->setAction($chunks[1]); + } + } + + private function hydrateStories(ApiLog $log, $chunks) + { + $log->setGeneral($chunks[0]); + $log->setResource($chunks[0]); + if ((int) $chunks[1] > 0 && (int) $chunks[2] > 0 && count($chunks) == 4) { + $log->setAspect($chunks[3]); + } + if (count($chunks) === 3 && (int) $chunks[1] > 0 && (int) $chunks[2] > 0) { + $log->setAction('get'); + } + } + + private function hydrateMonitor(ApiLog $log, $chunks) + { + $log->setGeneral($chunks[0]); + if (count($chunks) === 2) { + $log->setAspect($chunks[1]); + } + if (count($chunks) === 3 && (int) $chunks[2] > 0) { + $log->setAspect($chunks[1]); + $log->setAction('get'); + } + if (count($chunks) === 4) { + $log->setAspect($chunks[1]); + $log->setAction($chunks[3]); + } } } diff --git a/tests/Alchemy/Tests/Phrasea/Model/Manipulator/ApiLogManipulatorTest.php b/tests/Alchemy/Tests/Phrasea/Model/Manipulator/ApiLogManipulatorTest.php new file mode 100644 index 0000000000..6ea7d2c164 --- /dev/null +++ b/tests/Alchemy/Tests/Phrasea/Model/Manipulator/ApiLogManipulatorTest.php @@ -0,0 +1,99 @@ +create(self::$DI['oauth2-app-user'], self::$DI['user']); + $nbLogs = count(self::$DI['app']['repo.api-logs']->findAll()); + $manipulator = new ApiLogManipulator(self::$DI['app']['EM'], self::$DI['app']['repo.api-logs']); + $manipulator->create($account, Request::create('/databoxes/list/', 'POST'), new Response()); + $this->assertGreaterThan($nbLogs, count(self::$DI['app']['repo.api-accounts']->findAll())); + } + + /** + * @dataProvider apiRouteProvider + */ + public function testsLogHydration($path, $expected) + { + $emMock = $this->getMock('\Doctrine\ORM\EntityManager', array('persist', 'flush'), array(), '', false); + $account = $this->getMock('\Alchemy\Phrasea\Model\Entities\ApiAccount'); + $manipulator = new ApiLogManipulator($emMock, self::$DI['app']['repo.api-logs']); + $log = $manipulator->create($account, Request::create($path, 'POST'), new Response()); + $this->assertEquals($expected['resource'], $log->getResource()); + $this->assertEquals($expected['general'], $log->getGeneral()); + $this->assertEquals($expected['aspect'], $log->getAspect()); + $this->assertEquals($expected['action'], $log->getAction()); + } + + public function testDelete() + { + $manipulator = new ApiAccountManipulator(self::$DI['app']['EM'], self::$DI['app']['repo.api-accounts']); + $account = $manipulator->create(self::$DI['oauth2-app-user'], self::$DI['user']); + $manipulator = new ApiLogManipulator(self::$DI['app']['EM'], self::$DI['app']['repo.api-logs']); + $log = $manipulator->create($account, Request::create('/databoxes/list/', 'POST'), new Response()); + $countBefore = count(self::$DI['app']['repo.api-logs']->findAll()); + $manipulator->delete($log); + $this->assertGreaterThan(count(self::$DI['app']['repo.api-logs']->findAll()), $countBefore); + } + + public function testUpdate() + { + $manipulator = new ApiAccountManipulator(self::$DI['app']['EM'], self::$DI['app']['repo.api-accounts']); + $account = $manipulator->create(self::$DI['oauth2-app-user'], self::$DI['user']); + $manipulator = new ApiLogManipulator(self::$DI['app']['EM'], self::$DI['app']['repo.api-logs']); + $log = $manipulator->create($account, Request::create('/databoxes/list/', 'POST'), new Response()); + $log->setAspect('a-new-aspect'); + $manipulator->update($log); + $log = self::$DI['app']['repo.api-logs']->find($log->getId()); + $this->assertEquals('a-new-aspect', $log->getAspect()); + } + + public function apiRouteProvider() + { + return [ + ['/databoxes/list/', ['resource' => 'databoxes', 'aspect' => null, 'action' => 'list', 'general' => 'databoxes']], + ['/databoxes/1/collections/', ['resource' => 'databoxes', 'aspect' => 'collections', 'action' => null, 'general' => 'databoxes']], + ['/databoxes/1/status/', ['resource' => 'databoxes', 'aspect' => 'status', 'action' => null, 'general' => 'databoxes']], + ['/databoxes/1/metadatas/', ['resource' => 'databoxes', 'aspect' => 'metadatas', 'action' => null, 'general' => 'databoxes']], + ['/databoxes/1/termsOfUse/', ['resource' => 'databoxes', 'aspect' => 'termsOfUse', 'action' => null, 'general' => 'databoxes']], + ['/quarantine/list/', ['resource' => 'quarantine', 'aspect' => null, 'action' => 'list', 'general' => 'quarantine']], + ['/records/add/', ['resource' => 'records', 'aspect' => null, 'action' => 'add', 'general' => 'records']], + ['/records/search/', ['resource' => 'records', 'aspect' => null, 'action' => 'search', 'general' => 'records']], + ['/records/1/1/caption/', ['resource' => 'records', 'aspect' => 'caption', 'action' => null, 'general' => 'records']], + ['/records/1/1/metadatas/', ['resource' => 'records', 'aspect' => 'metadatas', 'action' => null, 'general' => 'records']], + ['/records/1/1/status/', ['resource' => 'records', 'aspect' => 'status', 'action' => null, 'general' => 'records']], + ['/records/1/1/embed/', ['resource' => 'records', 'aspect' => 'embed', 'action' => null, 'general' => 'records']], + ['/records/1/1/related/', ['resource' => 'records', 'aspect' => 'related', 'action' => null, 'general' => 'records']], + ['/records/1/1/', ['resource' => 'records', 'aspect' => null, 'action' => 'get', 'general' => 'records']], + ['/records/1/1/setstatus/', ['resource' => 'records', 'aspect' => null, 'action' => 'setstatus', 'general' => 'records']], + ['/records/1/1/setmetadatas/', ['resource' => 'records', 'aspect' => null, 'action' => 'setmetadatas', 'general' => 'records']], + ['/records/1/1/setcollection/', ['resource' => 'records', 'aspect' => null, 'action' => 'setcollection', 'general' => 'records']], + ['/stories/1/1/embed/', ['resource' => 'stories', 'aspect' => 'embed', 'action' => null, 'general' => 'stories']], + ['/stories/1/1/', ['resource' => 'stories', 'aspect' => null, 'action' => 'get', 'general' => 'stories']], + ['/baskets/add/', ['resource' => 'baskets', 'aspect' => null, 'action' => 'add', 'general' => 'baskets']], + ['/baskets/1/content/', ['resource' => 'baskets', 'aspect' => 'content', 'action' => '', 'general' => 'baskets']], + ['/baskets/1/delete/', ['resource' => 'baskets', 'aspect' => null, 'action' => 'delete', 'general' => 'baskets']], + ['/baskets/1/setdescription/', ['resource' => 'baskets', 'aspect' => null, 'action' => 'setdescription', 'general' => 'baskets']], + ['/baskets/1/setname/', ['resource' => 'baskets', 'aspect' => null, 'action' => 'setname', 'general' => 'baskets']], + ['/feeds/list/', ['resource' => 'feeds', 'aspect' => null, 'action' => 'list', 'general' => 'feeds']], + ['/feeds/1/content/', ['resource' => 'feeds', 'aspect' => 'content', 'action' => null, 'general' => 'feeds']], + ['/feeds/content/', ['resource' => 'feeds', 'aspect' => 'content', 'action' => null, 'general' => 'feeds']], + ['/feeds/entry/1/', ['resource' => 'feeds', 'aspect' => 'entry', 'action' => null, 'general' => 'feeds']], + ['/monitor/phraseanet/', ['resource' => null, 'aspect' => 'phraseanet', 'action' => null, 'general' => 'monitor']], + ['/monitor/scheduler/', ['resource' => null, 'aspect' => 'scheduler', 'action' => null, 'general' => 'monitor']], + ['/monitor/task/1/', ['resource' => null, 'aspect' => 'task', 'action' => 'get', 'general' => 'monitor']], + ['/monitor/task/1/stop/', ['resource' => null, 'aspect' => 'task', 'action' => 'stop', 'general' => 'monitor']], + ['/monitor/task/1/start/', ['resource' => null, 'aspect' => 'task', 'action' => 'start', 'general' => 'monitor']], + ['/monitor/tasks/', ['resource' => null, 'aspect' => 'tasks', 'action' => null, 'general' => 'monitor']], + ]; + } +} From 2e1a5057749209813aacc2af4739d13d9f51ebc9 Mon Sep 17 00:00:00 2001 From: Nicolas Le Goff Date: Fri, 7 Mar 2014 19:08:46 +0100 Subject: [PATCH 085/112] Create phraseanet oauth application when installation is done --- lib/Alchemy/Phrasea/Application.php | 2 + .../Phrasea/Core/Event/InstallFinishEvent.php | 31 ++++++++ .../Subscriber/PhraseaInstallSubscriber.php | 77 +++++++++++++++++++ lib/Alchemy/Phrasea/Core/PhraseaEvents.php | 2 + lib/Alchemy/Phrasea/Setup/Installer.php | 6 +- 5 files changed, 117 insertions(+), 1 deletion(-) create mode 100644 lib/Alchemy/Phrasea/Core/Event/InstallFinishEvent.php create mode 100644 lib/Alchemy/Phrasea/Core/Event/Subscriber/PhraseaInstallSubscriber.php diff --git a/lib/Alchemy/Phrasea/Application.php b/lib/Alchemy/Phrasea/Application.php index 7b8407d997..c8724f6b20 100644 --- a/lib/Alchemy/Phrasea/Application.php +++ b/lib/Alchemy/Phrasea/Application.php @@ -77,6 +77,7 @@ use Alchemy\Phrasea\Core\Event\Subscriber\LogoutSubscriber; use Alchemy\Phrasea\Core\Event\Subscriber\PhraseaLocaleSubscriber; use Alchemy\Phrasea\Core\Event\Subscriber\MaintenanceSubscriber; use Alchemy\Phrasea\Core\Event\Subscriber\CookiesDisablerSubscriber; +use Alchemy\Phrasea\Core\Event\Subscriber\PhraseaInstallSubscriber; use Alchemy\Phrasea\Core\Middleware\ApiApplicationMiddlewareProvider; use Alchemy\Phrasea\Core\Middleware\BasketMiddlewareProvider; use Alchemy\Phrasea\Core\Middleware\TokenMiddlewareProvider; @@ -473,6 +474,7 @@ class Application extends SilexApplication $dispatcher->addSubscriber(new PhraseaLocaleSubscriber($app)); $dispatcher->addSubscriber(new MaintenanceSubscriber($app)); $dispatcher->addSubscriber(new CookiesDisablerSubscriber($app)); + $dispatcher->addSubscriber(new PhraseaInstallSubscriber($app)); return $dispatcher; }) diff --git a/lib/Alchemy/Phrasea/Core/Event/InstallFinishEvent.php b/lib/Alchemy/Phrasea/Core/Event/InstallFinishEvent.php new file mode 100644 index 0000000000..b3a4ec1ccf --- /dev/null +++ b/lib/Alchemy/Phrasea/Core/Event/InstallFinishEvent.php @@ -0,0 +1,31 @@ +user = $user; + } + + public function getUser() + { + return $this->user; + } +} diff --git a/lib/Alchemy/Phrasea/Core/Event/Subscriber/PhraseaInstallSubscriber.php b/lib/Alchemy/Phrasea/Core/Event/Subscriber/PhraseaInstallSubscriber.php new file mode 100644 index 0000000000..ae0f9b415a --- /dev/null +++ b/lib/Alchemy/Phrasea/Core/Event/Subscriber/PhraseaInstallSubscriber.php @@ -0,0 +1,77 @@ +app = $app; + } + + public static function getSubscribedEvents() + { + return [ + PhraseaEvents::INSTALL_FINISH => 'onInstallFinished' + ]; + } + + public function onInstallFinished(InstallFinishEvent $event) + { + $this->createNavigatorApplication(); + $this->createOfficePluginApplication(); + } + + private function createNavigatorApplication() + { + $application = $this->app['manipulator.api-application']->create( + \API_OAuth2_Application_Navigator::CLIENT_NAME, + ApiApplication::DESKTOP_TYPE, + '', + 'http://www.phraseanet.com', + null, + ApiApplication::NATIVE_APP_REDIRECT_URI + ); + + $application->setGrantPassword(true); + $application->setClientId(\API_OAuth2_Application_Navigator::CLIENT_ID); + $application->setClientSecret(\API_OAuth2_Application_Navigator::CLIENT_SECRET); + + $this->app['manipulator.api-application']->update($application); + } + + private function createOfficePluginApplication() + { + $application = $this->app['manipulator.api-application']->create( + \API_OAuth2_Application_OfficePlugin::CLIENT_NAME, + ApiApplication::DESKTOP_TYPE, + '', + 'http://www.phraseanet.com', + null, + ApiApplication::NATIVE_APP_REDIRECT_URI + ); + + $application->setGrantPassword(true); + $application->setClientId(\API_OAuth2_Application_OfficePlugin::CLIENT_ID); + $application->setClientSecret(\API_OAuth2_Application_OfficePlugin::CLIENT_SECRET); + + $this->app['manipulator.api-application']->update($application); + } +} diff --git a/lib/Alchemy/Phrasea/Core/PhraseaEvents.php b/lib/Alchemy/Phrasea/Core/PhraseaEvents.php index 6cff63f9e2..c282f9e831 100644 --- a/lib/Alchemy/Phrasea/Core/PhraseaEvents.php +++ b/lib/Alchemy/Phrasea/Core/PhraseaEvents.php @@ -18,6 +18,8 @@ final class PhraseaEvents const PRE_AUTHENTICATE = 'phrasea.pre-authenticate'; const POST_AUTHENTICATE = 'phrasea.post-authenticate'; + const INSTALL_FINISH = "phrasea.install-finish"; + const API_OAUTH2_START = 'api.oauth2.start'; const API_OAUTH2_END = 'api.oauth2.end'; const API_LOAD_START = 'api.load.start'; diff --git a/lib/Alchemy/Phrasea/Setup/Installer.php b/lib/Alchemy/Phrasea/Setup/Installer.php index 40b7600337..eb4a304f3d 100644 --- a/lib/Alchemy/Phrasea/Setup/Installer.php +++ b/lib/Alchemy/Phrasea/Setup/Installer.php @@ -12,10 +12,12 @@ namespace Alchemy\Phrasea\Setup; use Alchemy\Phrasea\Application; +use Alchemy\Phrasea\Core\PhraseaEvents; +use Alchemy\Phrasea\Core\Event\InstallFinishEvent; +use Alchemy\Phrasea\Model\Entities\User; use Doctrine\DBAL\Driver\Connection; use Doctrine\DBAL\DBALException; use Doctrine\ORM\Tools\SchemaTool; -use Alchemy\Phrasea\Model\Entities\User; class Installer { @@ -46,6 +48,8 @@ class Installer throw $e; } + $this->app['dispatcher']->dispatch(PhraseaEvents::INSTALL_FINISH, new InstallFinishEvent($user)); + return $user; } From ec07e33c9fbd8342ad3f05ebdf5b12f933527a0b Mon Sep 17 00:00:00 2001 From: Nicolas Le Goff Date: Fri, 7 Mar 2014 19:11:44 +0100 Subject: [PATCH 086/112] Fix patch --- lib/classes/patch/390alpha17a.php | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/lib/classes/patch/390alpha17a.php b/lib/classes/patch/390alpha17a.php index 8c2c3d5185..05a96c6cdf 100644 --- a/lib/classes/patch/390alpha17a.php +++ b/lib/classes/patch/390alpha17a.php @@ -70,8 +70,8 @@ class patch_390alpha17a extends patchAbstract $em->getConnection()->executeUpdate( 'INSERT INTO ApiApplications ( - id, `type`, `name`, description, website - created, updated, client_id, client_secret, nonce + id, `type`, `name`, description, website, + created, updated, client_id, client_secret, nonce, redirect_uri, activated, grant_password, creator_id ) @@ -91,7 +91,7 @@ class patch_390alpha17a extends patchAbstract $em->getConnection()->executeUpdate( 'INSERT INTO ApiAccounts ( - id, user_id, revoked + id, user_id, revoked, api_version, created, application_id ) @@ -111,14 +111,14 @@ class patch_390alpha17a extends patchAbstract $em->getConnection()->executeUpdate( 'INSERT INTO ApiLogs ( - id, account_id, route, error_message + id, account_id, route, error_message, created, status_code, format, resource, - general, aspect, `action`, error_code, + general, aspect, `action`, error_code ) ( SELECT - api_log_id, api_account_id, api_log_route, api_log_error_message + api_log_id, api_account_id, api_log_route, api_log_error_message, api_log_date, api_log_status_code, api_log_format, api_log_resource, api_log_general, api_log_aspect, api_log_action, api_log_error_code FROM api_log @@ -132,13 +132,13 @@ class patch_390alpha17a extends patchAbstract $em->getConnection()->executeUpdate( 'INSERT INTO ApiOauthCodes ( - code, account_id, redirect_uri, expires + code, account_id, redirect_uri, expires, scope, created, updated ) ( SELECT - code, api_account_id, redirect_uri, expires + code, api_account_id, redirect_uri, expires, scope, NOW(), NOW() FROM api_oauth_codes INNER JOIN api_accounts ON (api_accounts.api_account_id = api_oauth_codes.api_account_id) @@ -151,13 +151,13 @@ class patch_390alpha17a extends patchAbstract $em->getConnection()->executeUpdate( 'INSERT INTO ApiOauthRefreshTokens ( - refresh_token, account_id, expires + refresh_token, account_id, expires, scope, created, updated ) ( SELECT - refresh_token, api_account_id, expires + refresh_token, api_account_id, expires, scope, NOW(), NOW() FROM api_oauth_refresh_tokens INNER JOIN api_accounts ON (api_accounts.api_account_id = api_oauth_refresh_tokens.api_account_id) @@ -171,13 +171,13 @@ class patch_390alpha17a extends patchAbstract $em->getConnection()->executeUpdate( 'INSERT INTO ApiOauthTokens ( - oauth_token, account_id, session_id, expires + oauth_token, account_id, session_id, expires, scope, created, updated ) ( SELECT - oauth_token, api_account_id, session_id, expires + oauth_token, api_account_id, session_id, expires, scope, NOW(), NOW() FROM api_oauth_tokens INNER JOIN api_accounts ON (api_accounts.api_account_id = api_oauth_tokens.api_account_id) From 8fc7ec37140629fd9edf4230b95ce1d929a9b6cc Mon Sep 17 00:00:00 2001 From: Nicolas Le Goff Date: Mon, 10 Mar 2014 15:15:24 +0100 Subject: [PATCH 087/112] Fix infinite loop --- .../Phrasea/Model/Manipulator/ApiOauthCodeManipulator.php | 2 +- .../Model/Manipulator/ApiOauthRefreshTokenManipulator.php | 2 +- .../Phrasea/Model/Manipulator/ApiOauthTokenManipulator.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/Alchemy/Phrasea/Model/Manipulator/ApiOauthCodeManipulator.php b/lib/Alchemy/Phrasea/Model/Manipulator/ApiOauthCodeManipulator.php index e7d25b9707..43022679c6 100644 --- a/lib/Alchemy/Phrasea/Model/Manipulator/ApiOauthCodeManipulator.php +++ b/lib/Alchemy/Phrasea/Model/Manipulator/ApiOauthCodeManipulator.php @@ -71,7 +71,7 @@ class ApiOauthCodeManipulator implements ManipulatorInterface { do { $code = $this->randomGenerator->generateString(16, TokenManipulator::LETTERS_AND_NUMBERS); - } while (null === $this->repository->find($code)); + } while (null !== $this->repository->find($code)); return $code; } diff --git a/lib/Alchemy/Phrasea/Model/Manipulator/ApiOauthRefreshTokenManipulator.php b/lib/Alchemy/Phrasea/Model/Manipulator/ApiOauthRefreshTokenManipulator.php index a65e8d5b1b..416888907a 100644 --- a/lib/Alchemy/Phrasea/Model/Manipulator/ApiOauthRefreshTokenManipulator.php +++ b/lib/Alchemy/Phrasea/Model/Manipulator/ApiOauthRefreshTokenManipulator.php @@ -70,7 +70,7 @@ class ApiOauthRefreshTokenManipulator implements ManipulatorInterface { do { $refreshToken = $this->randomGenerator->generateString(32, TokenManipulator::LETTERS_AND_NUMBERS); - } while (null === $this->repository->find($refreshToken)); + } while (null !== $this->repository->find($refreshToken)); return $refreshToken; } diff --git a/lib/Alchemy/Phrasea/Model/Manipulator/ApiOauthTokenManipulator.php b/lib/Alchemy/Phrasea/Model/Manipulator/ApiOauthTokenManipulator.php index 650f4a9c46..eb9408772a 100644 --- a/lib/Alchemy/Phrasea/Model/Manipulator/ApiOauthTokenManipulator.php +++ b/lib/Alchemy/Phrasea/Model/Manipulator/ApiOauthTokenManipulator.php @@ -87,7 +87,7 @@ class ApiOauthTokenManipulator implements ManipulatorInterface { do { $token = $this->randomGenerator->generateString(32, TokenManipulator::LETTERS_AND_NUMBERS); - } while (null === $this->repository->find($token)); + } while (null !== $this->repository->find($token)); return $token; } From 426816e2d325a4ff77b5eabb7aad10bb7b4b98cb Mon Sep 17 00:00:00 2001 From: Nicolas Le Goff Date: Mon, 10 Mar 2014 15:16:09 +0100 Subject: [PATCH 088/112] Checks if api tables exists before apllaying patch --- lib/classes/patch/390alpha17a.php | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/lib/classes/patch/390alpha17a.php b/lib/classes/patch/390alpha17a.php index 05a96c6cdf..5c167c1f4b 100644 --- a/lib/classes/patch/390alpha17a.php +++ b/lib/classes/patch/390alpha17a.php @@ -67,6 +67,9 @@ class patch_390alpha17a extends patchAbstract private function fillApplicationTable(EntityManager $em) { + if (false === $this->tableExists($em, 'api_applications')) { + return true; + } $em->getConnection()->executeUpdate( 'INSERT INTO ApiApplications ( @@ -88,6 +91,9 @@ class patch_390alpha17a extends patchAbstract private function fillAccountTable(EntityManager $em) { + if (false === $this->tableExists($em, 'api_accounts')) { + return true; + } $em->getConnection()->executeUpdate( 'INSERT INTO ApiAccounts ( @@ -108,6 +114,9 @@ class patch_390alpha17a extends patchAbstract private function fillLogTable(EntityManager $em) { + if (false === $this->tableExists($em, 'api_accounts')) { + return true; + } $em->getConnection()->executeUpdate( 'INSERT INTO ApiLogs ( @@ -129,6 +138,9 @@ class patch_390alpha17a extends patchAbstract private function fillCodeTable(EntityManager $em) { + if (false === $this->tableExists($em, 'api_oauth_codes')) { + return true; + } $em->getConnection()->executeUpdate( 'INSERT INTO ApiOauthCodes ( @@ -148,6 +160,9 @@ class patch_390alpha17a extends patchAbstract private function fillRefreshTokenTable(EntityManager $em) { + if (false === $this->tableExists($em, 'api_oauth_refresh_tokens')) { + return true; + } $em->getConnection()->executeUpdate( 'INSERT INTO ApiOauthRefreshTokens ( @@ -168,6 +183,9 @@ class patch_390alpha17a extends patchAbstract private function fillOauthTokenTable(EntityManager $em) { + if (false === $this->tableExists($em, 'api_oauth_tokens')) { + return true; + } $em->getConnection()->executeUpdate( 'INSERT INTO ApiOauthTokens ( From 2cc2387c87a766eca7461b51673811bf3ac9c958 Mon Sep 17 00:00:00 2001 From: Nicolas Le Goff Date: Mon, 10 Mar 2014 17:01:54 +0100 Subject: [PATCH 089/112] Fix migration SQLs --- lib/classes/patch/390alpha17a.php | 54 +++++++++++++++---------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/lib/classes/patch/390alpha17a.php b/lib/classes/patch/390alpha17a.php index 5c167c1f4b..294aaaa534 100644 --- a/lib/classes/patch/390alpha17a.php +++ b/lib/classes/patch/390alpha17a.php @@ -80,11 +80,11 @@ class patch_390alpha17a extends patchAbstract ) ( SELECT - application_id, `type`, `name`, description, website, - created_on, last_modified, client_id, client_secret, nonce, - redirect_uri, activated, grant_password, creator - FROM api_applications - INNER JOIN Users ON (Users.id = api_accounts.usr_id) + a.application_id, a.`type`, a.`name`, a.description, a.website, + a.created_on, a.last_modified, a.client_id, a.client_secret, a.nonce, + a.redirect_uri, a.activated, a.grant_password, creator + FROM api_applications a + INNER JOIN Users ON (Users.id = a.creator OR a.creator IS NULL) )' ); } @@ -103,11 +103,11 @@ class patch_390alpha17a extends patchAbstract ) ( SELECT - api_account_id, usr_id, revoked, - api_version, created, application_id - FROM api_accounts - INNER JOIN Users ON (Users.id = api_accounts.usr_id) - INNER JOIN api_applications ON (api_accounts.application_id = api_applications.application_id) + a.api_account_id, a.usr_id, a.revoked, + a.api_version, a.created, a.application_id + FROM api_accounts a + INNER JOIN Users ON (Users.id = a.usr_id) + INNER JOIN api_applications b ON (a.application_id = b.application_id) )' ); } @@ -127,11 +127,11 @@ class patch_390alpha17a extends patchAbstract ) ( SELECT - api_log_id, api_account_id, api_log_route, api_log_error_message, - api_log_date, api_log_status_code, api_log_format, api_log_resource, - api_log_general, api_log_aspect, api_log_action, api_log_error_code - FROM api_log - INNER JOIN api_accounts ON (api_accounts.api_account_id = api_log.api_account_id) + a.api_log_id, a.api_account_id, a.api_log_route, a.api_log_error_message, + a.api_log_date, a.api_log_status_code, a.api_log_format, a.api_log_ressource, + a.api_log_general, a.api_log_aspect, a.api_log_action, a.api_log_error_code + FROM api_logs a + INNER JOIN api_accounts b ON (b.api_account_id = a.api_account_id) )' ); } @@ -150,10 +150,10 @@ class patch_390alpha17a extends patchAbstract ) ( SELECT - code, api_account_id, redirect_uri, expires, - scope, NOW(), NOW() - FROM api_oauth_codes - INNER JOIN api_accounts ON (api_accounts.api_account_id = api_oauth_codes.api_account_id) + a.code, a.api_account_id, a.redirect_uri, a.expires, + a.scope, NOW(), NOW() + FROM api_oauth_codes a + INNER JOIN api_accounts b ON (b.api_account_id = a.api_account_id) )' ); } @@ -172,10 +172,10 @@ class patch_390alpha17a extends patchAbstract ) ( SELECT - refresh_token, api_account_id, expires, - scope, NOW(), NOW() - FROM api_oauth_refresh_tokens - INNER JOIN api_accounts ON (api_accounts.api_account_id = api_oauth_refresh_tokens.api_account_id) + a.refresh_token, a.api_account_id, a.expires, + a.scope, NOW(), NOW() + FROM api_oauth_refresh_tokens a + INNER JOIN api_accounts b ON (b.api_account_id = a.api_account_id) )' ); } @@ -195,10 +195,10 @@ class patch_390alpha17a extends patchAbstract ) ( SELECT - oauth_token, api_account_id, session_id, expires, - scope, NOW(), NOW() - FROM api_oauth_tokens - INNER JOIN api_accounts ON (api_accounts.api_account_id = api_oauth_tokens.api_account_id) + a.oauth_token, a.api_account_id, a.session_id, expires, + a.scope, NOW(), NOW() + FROM api_oauth_tokens a + INNER JOIN api_accounts b ON (b.api_account_id = a.api_account_id) )' ); } From 63b6de5fc86291f631b7ded78885cfc74280d614 Mon Sep 17 00:00:00 2001 From: Nicolas Le Goff Date: Wed, 12 Mar 2014 11:39:55 +0100 Subject: [PATCH 090/112] Remove one to one relation beetween account and token & add lastUsed field for token --- lib/Alchemy/Phrasea/Controller/Api/V1.php | 5 ++- .../Phrasea/Controller/Root/Account.php | 6 ++-- .../Phrasea/Controller/Root/Developers.php | 17 ++++----- .../Phrasea/Model/Entities/ApiAccount.php | 36 ------------------- .../Phrasea/Model/Entities/ApiOauthToken.php | 26 ++++++++++++++ .../Manipulator/ApiOauthTokenManipulator.php | 2 -- .../Repositories/ApiOauthTokenRepository.php | 14 ++++++++ lib/classes/API/OAuth2/Adapter.php | 10 +++--- .../web/developers/application.html.twig | 8 ++--- 9 files changed, 67 insertions(+), 57 deletions(-) diff --git a/lib/Alchemy/Phrasea/Controller/Api/V1.php b/lib/Alchemy/Phrasea/Controller/Api/V1.php index 0a106494d0..bd80a1a082 100644 --- a/lib/Alchemy/Phrasea/Controller/Api/V1.php +++ b/lib/Alchemy/Phrasea/Controller/Api/V1.php @@ -62,7 +62,10 @@ class V1 implements ControllerProviderInterface }); $controllers->after(function (Request $request, Response $response) use ($app) { - $app['manipulator.api-log']->create($app['session']->get('token')->getAccount(), $request, $response); + $token = $app['session']->get('token'); + $app['manipulator.api-log']->create($token->getAccount(), $request, $response); + $token->setLastUsed(new \DateTime()); + $app['manipulator.api-oauth-token']->update($token); $app['session']->set('token', null); if (null !== $app['authentication']->getUser()) { $app['authentication']->closeAccount(); diff --git a/lib/Alchemy/Phrasea/Controller/Root/Account.php b/lib/Alchemy/Phrasea/Controller/Root/Account.php index 55dd002058..3fe807f5b0 100644 --- a/lib/Alchemy/Phrasea/Controller/Root/Account.php +++ b/lib/Alchemy/Phrasea/Controller/Root/Account.php @@ -243,9 +243,11 @@ class Account implements ControllerProviderInterface { $data = []; - foreach($app['repo.api-applications']->findByUser($app['authentication']->getUser()) as $application) { + foreach ($app['repo.api-applications']->findByUser($app['authentication']->getUser()) as $application) { + $account = $app['repo.api-accounts']->findByUserAndApplication($app['authentication']->getUser(), $application); + $data[$application->getId()]['application'] = $application; - $data[$application->getId()]['user-account'] = $app['repo.api-accounts']->findByUserAndApplication($app['authentication']->getUser(), $application); + $data[$application->getId()]['user-account'] = $account; } return $app['twig']->render('account/authorized_apps.html.twig', [ diff --git a/lib/Alchemy/Phrasea/Controller/Root/Developers.php b/lib/Alchemy/Phrasea/Controller/Root/Developers.php index bf75c17355..9406b7cc6f 100644 --- a/lib/Alchemy/Phrasea/Controller/Root/Developers.php +++ b/lib/Alchemy/Phrasea/Controller/Root/Developers.php @@ -130,14 +130,14 @@ class Developers implements ControllerProviderInterface $app->abort(404, sprintf('Account not found for application %s', $application->getName())); } - $token = $account->getOauthToken(); - if ($account->hasOauthToken()) { - $app['manipulator.api-oauth-token']->renew($token); + if(null !== $devToken = $app['repo.api-oauth-tokens']->findDeveloperToken($account)) { + $app['manipulator.api-oauth-token']->renew($devToken); } else { - $token = $app['manipulator.api-oauth-token']->create($account); + // dev tokens do not expires + $devToken = $app['manipulator.api-oauth-token']->create($account); } - return $app->json(['success' => true, 'token' => $token->getOauthToken()]); + return $app->json(['success' => true, 'token' => $devToken->getOauthToken()]); } /** @@ -188,6 +188,9 @@ class Developers implements ControllerProviderInterface sprintf('%s%s', $form->getSchemeCallback(), $form->getCallback()) ); + // create an account as well + $app['manipulator.api-account']->create($application, $app['authentication']->getUser()); + return $app->redirectPath('developers_application', ['application' => $application->getId()]); } @@ -241,9 +244,7 @@ class Developers implements ControllerProviderInterface $token = null; if (null !== $account = $app['repo.api-accounts']->findByUserAndApplication($app['authentication']->getUser(), $application)) { - if ($account->hasOauthToken()) { - $token = $account->getOauthToken()->getOauthToken(); - } + $token = $app['repo.api-oauth-tokens']->findDeveloperToken($account); } return $app['twig']->render('developers/application.html.twig', [ diff --git a/lib/Alchemy/Phrasea/Model/Entities/ApiAccount.php b/lib/Alchemy/Phrasea/Model/Entities/ApiAccount.php index d2fa72f04d..a942242e39 100644 --- a/lib/Alchemy/Phrasea/Model/Entities/ApiAccount.php +++ b/lib/Alchemy/Phrasea/Model/Entities/ApiAccount.php @@ -48,14 +48,6 @@ class ApiAccount **/ private $application; - /** - * @ORM\OneToOne(targetEntity="ApiOauthToken", inversedBy="account") - * @ORM\JoinColumn(name="oauth_token", referencedColumnName="oauth_token", nullable=true) - * - * @return ApiApplication - **/ - private $oauthToken; - /** * @Gedmo\Timestampable(on="create") * @ORM\Column(type="datetime") @@ -169,32 +161,4 @@ class ApiAccount { return $this->user; } - - /** - * @param ApiOauthToken $oauthToken - * - * @return ApiAccount - */ - public function setOauthToken(ApiOauthToken $oauthToken) - { - $this->oauthToken = $oauthToken; - - return $this; - } - - /** - * @return ApiOauthToken - */ - public function getOauthToken() - { - return $this->oauthToken; - } - - /** - * @return boolean - */ - public function hasOauthToken() - { - return null !== $this->oauthToken; - } } diff --git a/lib/Alchemy/Phrasea/Model/Entities/ApiOauthToken.php b/lib/Alchemy/Phrasea/Model/Entities/ApiOauthToken.php index 5a2da03271..59e1d83bee 100644 --- a/lib/Alchemy/Phrasea/Model/Entities/ApiOauthToken.php +++ b/lib/Alchemy/Phrasea/Model/Entities/ApiOauthToken.php @@ -39,6 +39,12 @@ class ApiOauthToken */ private $expires; + /** + * @Gedmo\Timestampable(on="create") + * @ORM\Column(type="datetime", name="last_used") + */ + private $lastUsed; + /** * @var string * @@ -197,4 +203,24 @@ class ApiOauthToken { return $this->updated; } + + /** + * @param \DateTime $lastUsed + * + * @return ApiOauthToken + */ + public function setLastUsed(\DateTime $lastUsed) + { + $this->lastUsed = $lastUsed; + + return $this; + } + + /** + * @return \DateTime + */ + public function getLastUsed() + { + return $this->lastUsed; + } } diff --git a/lib/Alchemy/Phrasea/Model/Manipulator/ApiOauthTokenManipulator.php b/lib/Alchemy/Phrasea/Model/Manipulator/ApiOauthTokenManipulator.php index eb9408772a..ded2abdc3d 100644 --- a/lib/Alchemy/Phrasea/Model/Manipulator/ApiOauthTokenManipulator.php +++ b/lib/Alchemy/Phrasea/Model/Manipulator/ApiOauthTokenManipulator.php @@ -43,8 +43,6 @@ class ApiOauthTokenManipulator implements ManipulatorInterface $token->setScope($scope); $token->setAccount($account); - $account->setOauthToken($token); - $this->om->persist($account); $this->update($token); diff --git a/lib/Alchemy/Phrasea/Model/Repositories/ApiOauthTokenRepository.php b/lib/Alchemy/Phrasea/Model/Repositories/ApiOauthTokenRepository.php index 20418a24f7..198dc643fd 100644 --- a/lib/Alchemy/Phrasea/Model/Repositories/ApiOauthTokenRepository.php +++ b/lib/Alchemy/Phrasea/Model/Repositories/ApiOauthTokenRepository.php @@ -2,7 +2,9 @@ namespace Alchemy\Phrasea\Model\Repositories; +use Alchemy\Phrasea\Model\Entities\ApiAccount; use Doctrine\ORM\EntityRepository; +use Doctrine\ORM\Query\Expr; /** * ApiOauthTokenRepository @@ -12,4 +14,16 @@ use Doctrine\ORM\EntityRepository; */ class ApiOauthTokenRepository extends EntityRepository { + public function findDeveloperToken(ApiAccount $account) + { + $qb = $this->createQueryBuilder('tok'); + $qb->innerJoin('tok.account', 'acc', Expr\Join::WITH, $qb->expr()->eq('acc.id', ':acc_id')); + $qb->innerJoin('acc.application', 'app', Expr\Join::WITH, $qb->expr()->orx( + $qb->expr()->eq('app.creator', 'acc.user'), + $qb->expr()->isNull('app.creator') + )); + $qb->setParameter(':acc_id', $account->getId()); + + return $qb->getQuery()->getOneOrNullResult(); + } } diff --git a/lib/classes/API/OAuth2/Adapter.php b/lib/classes/API/OAuth2/Adapter.php index 180c4ccdc9..48000673c1 100644 --- a/lib/classes/API/OAuth2/Adapter.php +++ b/lib/classes/API/OAuth2/Adapter.php @@ -217,13 +217,13 @@ class API_OAuth2_Adapter extends OAuth2 * @return $this * @throws RuntimeException */ - protected function setAccessToken($oauthToken, $accountId, $expires, $scope = null) + protected function setAccessToken($oauthToken, $accountId, $expires = null, $scope = null) { if (null === $account = $this->app['repo.api-accounts']->find($accountId)) { throw new RuntimeException(sprintf('Account with id %s is not valid', $accountId)); } - - $token = $this->app['manipulator.api-oauth-token']->create($account, null, \DateTime::createFromFormat('U', $expires), $scope); + $expires = null === $expires ? $expires : \DateTime::createFromFormat('U', $expires); + $token = $this->app['manipulator.api-oauth-token']->create($account, $expires, $scope); $this->app['manipulator.api-oauth-token']->setOauthToken($token, $oauthToken); return $this; @@ -764,11 +764,13 @@ class API_OAuth2_Adapter extends OAuth2 "scope" => $scope ]; + $expires = null; if ($this->enable_expire) { $token['expires_in'] = $this->getVariable('access_token_lifetime', OAUTH2_DEFAULT_ACCESS_TOKEN_LIFETIME); + $expires = time() + $this->getVariable('access_token_lifetime', OAUTH2_DEFAULT_ACCESS_TOKEN_LIFETIME); } - $this->setAccessToken($token["access_token"], $accountId, time() + $this->getVariable('access_token_lifetime', OAUTH2_DEFAULT_ACCESS_TOKEN_LIFETIME), $scope); + $this->setAccessToken($token["access_token"], $accountId, $expires, $scope); // Issue a refresh token also, if we support them if (in_array(OAUTH2_GRANT_TYPE_REFRESH_TOKEN, $this->getSupportedGrantTypes())) { diff --git a/templates/web/developers/application.html.twig b/templates/web/developers/application.html.twig index 8be5de858f..800acab513 100644 --- a/templates/web/developers/application.html.twig +++ b/templates/web/developers/application.html.twig @@ -81,18 +81,18 @@ +
- {% if not token is none %} - {{ token|default("") }} + {% if not token is none %} + {{ token.getOauthToken()|default("") }} {% else %} {{ "Le token n\'a pas encore ete genere" | trans }} {% endif %} {{ "boutton::generer" | trans }} -
- From 178da6a3a6fc3d321158cca92b4a5e6dc8006874 Mon Sep 17 00:00:00 2001 From: Nicolas Le Goff Date: Wed, 12 Mar 2014 12:32:05 +0100 Subject: [PATCH 091/112] Fix tests --- .../Repositories/ApiOauthTokenRepository.php | 23 ++- .../Phrasea/Controller/Api/ApiTestCase.php | 142 +++++++++++------- 2 files changed, 107 insertions(+), 58 deletions(-) diff --git a/lib/Alchemy/Phrasea/Model/Repositories/ApiOauthTokenRepository.php b/lib/Alchemy/Phrasea/Model/Repositories/ApiOauthTokenRepository.php index 198dc643fd..6396ab3a97 100644 --- a/lib/Alchemy/Phrasea/Model/Repositories/ApiOauthTokenRepository.php +++ b/lib/Alchemy/Phrasea/Model/Repositories/ApiOauthTokenRepository.php @@ -22,8 +22,29 @@ class ApiOauthTokenRepository extends EntityRepository $qb->expr()->eq('app.creator', 'acc.user'), $qb->expr()->isNull('app.creator') )); + $qb->where($qb->expr()->isNull('tok.expires')); + $qb->orderBy('tok.created', 'DESC'); $qb->setParameter(':acc_id', $account->getId()); - return $qb->getQuery()->getOneOrNullResult(); + /* + * @note until we add expiration token, there is no way to distinguish a developer issued token from + * a connection process issued token. + */ + $tokens = $qb->getQuery()->getResult(); + + if (0 === count($tokens)) { + return null; + } + + return current($tokens); + } + + public function findOauthTokens(ApiAccount $account) + { + $qb = $this->createQueryBuilder('tok'); + $qb->where($qb->expr()->eq('tok.account', ':acc')); + $qb->setParameter(':acc', $account); + + return $qb->getQuery()->getResult(); } } diff --git a/tests/Alchemy/Tests/Phrasea/Controller/Api/ApiTestCase.php b/tests/Alchemy/Tests/Phrasea/Controller/Api/ApiTestCase.php index 3fe693c693..a888ed044f 100644 --- a/tests/Alchemy/Tests/Phrasea/Controller/Api/ApiTestCase.php +++ b/tests/Alchemy/Tests/Phrasea/Controller/Api/ApiTestCase.php @@ -22,6 +22,9 @@ abstract class ApiTestCase extends \PhraseanetWebTestCase abstract protected function unserialize($data); abstract protected function getAcceptMimeType(); + private $adminAccessToken; + private $userAccessToken; + public function tearDown() { $this->unsetToken(); @@ -35,6 +38,31 @@ abstract class ApiTestCase extends \PhraseanetWebTestCase self::$DI['app'] = self::$DI->share(function ($DI) { return $this->loadApp('lib/Alchemy/Phrasea/Application/Api.php'); }); + + if (null === $this->adminAccessToken) { + $tokens = self::$DI['app']['repo.api-oauth-tokens']->findOauthTokens(self::$DI['oauth2-app-acc-user']); + if (count($tokens) === 0) { + $this->fail(sprintf('No access token generated between user %s & application %s', + self::$DI['oauth2-app-acc-user']->getUser()->getLogin(), + self::$DI['oauth2-app-acc-user']->getApplication()->getName() + )); + } + + $this->adminAccessToken = current($tokens); + } + + + if (null === $this->userAccessToken) { + $tokens = self::$DI['app']['repo.api-oauth-tokens']->findOauthTokens(self::$DI['oauth2-app-acc-user-not-admin']); + if (count($tokens) === 0) { + $this->fail(sprintf('No access token generated between user %s & application %s', + self::$DI['oauth2-app-acc-user-not-admin']->getUser()->getLogin(), + self::$DI['oauth2-app-acc-user-not-admin']->getApplication()->getName() + )); + } + + $this->userAccessToken = current($tokens); + } } /** @@ -51,7 +79,7 @@ abstract class ApiTestCase extends \PhraseanetWebTestCase } }); - $this->setToken(self::$DI['oauth2-app-acc-user-not-admin']->getOauthToken()->getOauthToken()); + $this->setToken($this->userAccessToken); self::$DI['client']->request('GET', $route, $this->getParameters(), [], ['HTTP_Accept' => $this->getAcceptMimeType()]); $this->assertEquals(1, $preEvent); @@ -60,7 +88,7 @@ abstract class ApiTestCase extends \PhraseanetWebTestCase public function testThatSessionIsClosedAfterRequest() { $this->assertCount(0, self::$DI['app']['EM']->getRepository('Phraseanet:Session')->findAll()); - $this->setToken(self::$DI['oauth2-app-acc-user-not-admin']->getOauthToken()->getOauthToken()); + $this->setToken($this->userAccessToken); self::$DI['client']->request('GET', '/api/v1/databoxes/list/', $this->getParameters(), [], ['HTTP_Accept' => $this->getAcceptMimeType()]); $this->assertCount(0, self::$DI['app']['EM']->getRepository('Phraseanet:Session')->findAll()); } @@ -79,7 +107,7 @@ abstract class ApiTestCase extends \PhraseanetWebTestCase public function testRouteNotFound() { $route = '/api/v1/nothinghere'; - $this->setToken(self::$DI['oauth2-app-acc-user-not-admin']->getOauthToken()->getOauthToken()); + $this->setToken($this->userAccessToken); self::$DI['client']->request('GET', $route, $this->getParameters(), [], ['HTTP_Accept' => $this->getAcceptMimeType()]); $content = $this->unserialize(self::$DI['client']->getResponse()->getContent()); @@ -89,7 +117,7 @@ abstract class ApiTestCase extends \PhraseanetWebTestCase public function testDataboxListRoute() { - $this->setToken(self::$DI['oauth2-app-acc-user-not-admin']->getOauthToken()->getOauthToken()); + $this->setToken($this->userAccessToken); self::$DI['client']->request('GET', '/api/v1/databoxes/list/', $this->getParameters(), [], ['HTTP_Accept' => $this->getAcceptMimeType()]); $content = $this->unserialize(self::$DI['client']->getResponse()->getContent()); @@ -150,7 +178,7 @@ abstract class ApiTestCase extends \PhraseanetWebTestCase */ public function testAdminOnlyShedulerState() { - $this->setToken(self::$DI['oauth2-app-acc-user-not-admin']->getOauthToken()->getOauthToken()); + $this->setToken($this->userAccessToken); self::$DI['client']->request('GET', '/api/v1/monitor/tasks/', $this->getParameters(), [], ['HTTP_Accept' => $this->getAcceptMimeType()]); $content = $this->unserialize(self::$DI['client']->getResponse()->getContent()); @@ -186,7 +214,7 @@ abstract class ApiTestCase extends \PhraseanetWebTestCase */ public function testGetMonitorTasks() { - $this->setToken(self::$DI['oauth2-app-acc-user']->getOauthToken()->getOauthToken()); + $this->setToken($this->adminAccessToken); $route = '/api/v1/monitor/tasks/'; $this->evaluateMethodNotAllowedRoute($route, ['POST', 'PUT', 'DELETE']); @@ -211,7 +239,7 @@ abstract class ApiTestCase extends \PhraseanetWebTestCase */ public function testGetScheduler() { - $this->setToken(self::$DI['oauth2-app-acc-user']->getOauthToken()->getOauthToken()); + $this->setToken($this->adminAccessToken); $route = '/api/v1/monitor/scheduler/'; $this->evaluateMethodNotAllowedRoute($route, ['POST', 'PUT', 'DELETE']); @@ -291,7 +319,7 @@ abstract class ApiTestCase extends \PhraseanetWebTestCase $this->markTestSkipped('no tasks created for the current instance'); } - $this->setToken(self::$DI['oauth2-app-acc-user']->getOauthToken()->getOauthToken()); + $this->setToken($this->adminAccessToken); $idTask = $tasks[0]->getId(); $route = '/api/v1/monitor/task/' . $idTask . '/'; @@ -314,7 +342,7 @@ abstract class ApiTestCase extends \PhraseanetWebTestCase $this->markTestSkipped('no tasks created for the current instance'); } - $this->setToken(self::$DI['oauth2-app-acc-user']->getOauthToken()->getOauthToken()); + $this->setToken($this->adminAccessToken); $idTask = $tasks[0]->getId(); $route = '/api/v1/monitor/task/' . $idTask . '/'; @@ -334,10 +362,10 @@ abstract class ApiTestCase extends \PhraseanetWebTestCase public function testUnknowGetMonitorTaskById() { - if (null === self::$DI['oauth2-app-acc-user']->getOauthToken()->getOauthToken()) { + if (null === $this->adminAccessToken) { $this->markTestSkipped('no tasks created for the current instance'); } - $this->setToken(self::$DI['oauth2-app-acc-user']->getOauthToken()->getOauthToken()); + $this->setToken($this->adminAccessToken); self::$DI['client']->followRedirects(); self::$DI['client']->request('GET', '/api/v1/monitor/task/0/', $this->getParameters(), [], ['HTTP_Accept' => $this->getAcceptMimeType()]); $content = $this->unserialize(self::$DI['client']->getResponse()->getContent()); @@ -352,7 +380,7 @@ abstract class ApiTestCase extends \PhraseanetWebTestCase $this->markTestSkipped('no tasks created for the current instance'); } - $this->setToken(self::$DI['oauth2-app-acc-user']->getOauthToken()->getOauthToken()); + $this->setToken($this->adminAccessToken); $idTask = $tasks[0]->getId(); $route = '/api/v1/monitor/task/' . $idTask . '/start/'; @@ -378,7 +406,7 @@ abstract class ApiTestCase extends \PhraseanetWebTestCase $this->markTestSkipped('no tasks created for the current instance'); } - $this->setToken(self::$DI['oauth2-app-acc-user']->getOauthToken()->getOauthToken()); + $this->setToken($this->adminAccessToken); $idTask = $tasks[0]->getId(); $route = '/api/v1/monitor/task/' . $idTask . '/stop/'; @@ -400,7 +428,7 @@ abstract class ApiTestCase extends \PhraseanetWebTestCase { self::$DI['app']['phraseanet.SE'] = $this->createSearchEngineMock(); - $this->setToken(self::$DI['oauth2-app-acc-user']->getOauthToken()->getOauthToken()); + $this->setToken($this->adminAccessToken); self::$DI['client']->request('GET', '/api/v1/monitor/phraseanet/', $this->getParameters(), [], ['HTTP_Accept' => $this->getAcceptMimeType()]); $content = $this->unserialize(self::$DI['client']->getResponse()->getContent()); @@ -418,7 +446,7 @@ abstract class ApiTestCase extends \PhraseanetWebTestCase public function testRecordRoute() { - $this->setToken(self::$DI['oauth2-app-acc-user-not-admin']->getOauthToken()->getOauthToken()); + $this->setToken($this->userAccessToken); $route = '/api/v1/records/' . self::$DI['record_1']->get_sbas_id() . '/' . self::$DI['record_1']->get_record_id() . '/'; $this->evaluateMethodNotAllowedRoute($route, ['POST', 'PUT', 'DELETE']); @@ -440,7 +468,7 @@ abstract class ApiTestCase extends \PhraseanetWebTestCase public function testStoryRoute() { - $this->setToken(self::$DI['oauth2-app-acc-user-not-admin']->getOauthToken()->getOauthToken()); + $this->setToken($this->userAccessToken); self::$DI['app']['session']->set('usr_id', self::$DI['user']->getId()); if (false === self::$DI['record_story_1']->hasChild(self::$DI['record_1'])) { self::$DI['record_story_1']->appendChild(self::$DI['record_1']); @@ -471,7 +499,7 @@ abstract class ApiTestCase extends \PhraseanetWebTestCase public function testDataboxCollectionRoute() { - $this->setToken(self::$DI['oauth2-app-acc-user-not-admin']->getOauthToken()->getOauthToken()); + $this->setToken($this->userAccessToken); $databox_id = self::$DI['record_1']->get_sbas_id(); $route = '/api/v1/databoxes/' . $databox_id . '/collections/'; $this->evaluateMethodNotAllowedRoute($route, ['POST', 'PUT', 'DELETE']); @@ -511,7 +539,7 @@ abstract class ApiTestCase extends \PhraseanetWebTestCase public function testDataboxStatusRoute() { - $this->setToken(self::$DI['oauth2-app-acc-user-not-admin']->getOauthToken()->getOauthToken()); + $this->setToken($this->userAccessToken); $databox_id = self::$DI['record_1']->get_sbas_id(); $databox = self::$DI['app']['phraseanet.appbox']->get_databox($databox_id); $ref_status = $databox->get_statusbits(); @@ -560,7 +588,7 @@ abstract class ApiTestCase extends \PhraseanetWebTestCase public function testDataboxMetadatasRoute() { - $this->setToken(self::$DI['oauth2-app-acc-user-not-admin']->getOauthToken()->getOauthToken()); + $this->setToken($this->userAccessToken); $databox_id = self::$DI['record_1']->get_sbas_id(); $databox = self::$DI['app']['phraseanet.appbox']->get_databox($databox_id); $ref_structure = $databox->get_meta_structure(); @@ -643,7 +671,7 @@ abstract class ApiTestCase extends \PhraseanetWebTestCase public function testDataboxTermsOfUseRoute() { - $this->setToken(self::$DI['oauth2-app-acc-user-not-admin']->getOauthToken()->getOauthToken()); + $this->setToken($this->userAccessToken); $databox_id = self::$DI['record_1']->get_sbas_id(); $route = '/api/v1/databoxes/' . $databox_id . '/termsOfUse/'; $this->evaluateMethodNotAllowedRoute($route, ['POST', 'PUT', 'DELETE']); @@ -682,7 +710,7 @@ abstract class ApiTestCase extends \PhraseanetWebTestCase self::$DI['app']['manipulator.user']->expects($this->once())->method('logQuery'); - $this->setToken(self::$DI['oauth2-app-acc-user-not-admin']->getOauthToken()->getOauthToken()); + $this->setToken($this->userAccessToken); self::$DI['client']->request('POST', '/api/v1/search/', $this->getParameters(), [], ['HTTP_Accept' => $this->getAcceptMimeType()]); $content = $this->unserialize(self::$DI['client']->getResponse()->getContent()); @@ -715,7 +743,7 @@ abstract class ApiTestCase extends \PhraseanetWebTestCase $this->markTestSkipped('Phrasea2 extension is required for this test'); } - $this->setToken(self::$DI['oauth2-app-acc-user-not-admin']->getOauthToken()->getOauthToken()); + $this->setToken($this->userAccessToken); self::$DI['record_story_1']; @@ -751,7 +779,7 @@ abstract class ApiTestCase extends \PhraseanetWebTestCase $this->markTestSkipped('Phrasea2 extension is required for this test'); } - $this->setToken(self::$DI['oauth2-app-acc-user-not-admin']->getOauthToken()->getOauthToken()); + $this->setToken($this->userAccessToken); self::$DI['client']->request('POST', '/api/v1/records/search/', $this->getParameters(), [], ['HTTP_Accept' => $this->getAcceptMimeType()]); $content = $this->unserialize(self::$DI['client']->getResponse()->getContent()); @@ -773,7 +801,7 @@ abstract class ApiTestCase extends \PhraseanetWebTestCase */ public function testRecordsSearchRouteWithQuery($method) { - $this->setToken(self::$DI['oauth2-app-acc-user-not-admin']->getOauthToken()->getOauthToken()); + $this->setToken($this->userAccessToken); $searchEngine = $this->getMockBuilder('Alchemy\Phrasea\SearchEngine\SearchEngineResult') ->disableOriginalConstructor() ->getMock(); @@ -802,7 +830,7 @@ abstract class ApiTestCase extends \PhraseanetWebTestCase public function testRecordsCaptionRoute() { - $this->setToken(self::$DI['oauth2-app-acc-user-not-admin']->getOauthToken()->getOauthToken()); + $this->setToken($this->userAccessToken); self::$DI['app']['phraseanet.SE'] = $this->createSearchEngineMock(); $this->injectMetadatas(self::$DI['record_1']); @@ -828,7 +856,7 @@ abstract class ApiTestCase extends \PhraseanetWebTestCase public function testRecordsMetadatasRoute() { - $this->setToken(self::$DI['oauth2-app-acc-user-not-admin']->getOauthToken()->getOauthToken()); + $this->setToken($this->userAccessToken); $route = '/api/v1/records/' . self::$DI['record_1']->get_sbas_id() . '/' . self::$DI['record_1']->get_record_id() . '/metadatas/'; $this->evaluateMethodNotAllowedRoute($route, ['POST', 'PUT', 'DELETE']); @@ -851,7 +879,7 @@ abstract class ApiTestCase extends \PhraseanetWebTestCase public function testRecordsStatusRoute() { - $this->setToken(self::$DI['oauth2-app-acc-user-not-admin']->getOauthToken()->getOauthToken()); + $this->setToken($this->userAccessToken); $route = '/api/v1/records/' . self::$DI['record_1']->get_sbas_id() . '/' . self::$DI['record_1']->get_record_id() . '/status/'; $this->evaluateMethodNotAllowedRoute($route, ['POST', 'PUT', 'DELETE']); @@ -874,7 +902,7 @@ abstract class ApiTestCase extends \PhraseanetWebTestCase public function testRecordsEmbedRoute() { - $this->setToken(self::$DI['oauth2-app-acc-user-not-admin']->getOauthToken()->getOauthToken()); + $this->setToken($this->userAccessToken); $route = '/api/v1/records/' . self::$DI['record_1']->get_sbas_id() . '/' . self::$DI['record_1']->get_record_id() . '/embed/'; $this->evaluateMethodNotAllowedRoute($route, ['POST', 'PUT', 'DELETE']); @@ -900,7 +928,7 @@ abstract class ApiTestCase extends \PhraseanetWebTestCase public function testStoriesEmbedRoute() { - $this->setToken(self::$DI['oauth2-app-acc-user-not-admin']->getOauthToken()->getOauthToken()); + $this->setToken($this->userAccessToken); $story = self::$DI['record_story_1']; $route = '/api/v1/stories/' . $story->get_sbas_id() . '/' . $story->get_record_id() . '/embed/'; @@ -927,7 +955,7 @@ abstract class ApiTestCase extends \PhraseanetWebTestCase public function testRecordsEmbedRouteMimeType() { - $this->setToken(self::$DI['oauth2-app-acc-user-not-admin']->getOauthToken()->getOauthToken()); + $this->setToken($this->userAccessToken); $route = '/api/v1/records/' . self::$DI['record_1']->get_sbas_id() . '/' . self::$DI['record_1']->get_record_id() . '/embed/'; @@ -941,7 +969,7 @@ abstract class ApiTestCase extends \PhraseanetWebTestCase public function testRecordsEmbedRouteDevices() { - $this->setToken(self::$DI['oauth2-app-acc-user-not-admin']->getOauthToken()->getOauthToken()); + $this->setToken($this->userAccessToken); $route = '/api/v1/records/' . self::$DI['record_1']->get_sbas_id() . '/' . self::$DI['record_1']->get_record_id() . '/embed/'; @@ -953,7 +981,7 @@ abstract class ApiTestCase extends \PhraseanetWebTestCase public function testRecordsRelatedRoute() { - $this->setToken(self::$DI['oauth2-app-acc-user-not-admin']->getOauthToken()->getOauthToken()); + $this->setToken($this->userAccessToken); $route = '/api/v1/records/' . self::$DI['record_1']->get_sbas_id() . '/' . self::$DI['record_1']->get_record_id() . '/related/'; $this->evaluateMethodNotAllowedRoute($route, ['POST', 'PUT', 'DELETE']); @@ -980,7 +1008,7 @@ abstract class ApiTestCase extends \PhraseanetWebTestCase public function testRecordsSetMetadatas() { self::$DI['app']['phraseanet.SE'] = $this->createSearchEngineMock(); - $this->setToken(self::$DI['oauth2-app-acc-user-not-admin']->getOauthToken()->getOauthToken()); + $this->setToken($this->userAccessToken); $record = self::$DI['record_1']; @@ -1038,7 +1066,7 @@ abstract class ApiTestCase extends \PhraseanetWebTestCase public function testRecordsSetStatus() { self::$DI['app']['phraseanet.SE'] = $this->createSearchEngineMock(); - $this->setToken(self::$DI['oauth2-app-acc-user-not-admin']->getOauthToken()->getOauthToken()); + $this->setToken($this->userAccessToken); $route = '/api/v1/records/' . self::$DI['record_1']->get_sbas_id() . '/' . self::$DI['record_1']->get_record_id() . '/setstatus/'; @@ -1100,7 +1128,7 @@ abstract class ApiTestCase extends \PhraseanetWebTestCase $file = new File(self::$DI['app'], self::$DI['app']['mediavorus']->guess(__DIR__ . '/../../../../../files/test001.jpg'), self::$DI['collection']); $record = \record_adapter::createFromFile($file, self::$DI['app']); - $this->setToken(self::$DI['oauth2-app-acc-user-not-admin']->getOauthToken()->getOauthToken()); + $this->setToken($this->userAccessToken); $route = '/api/v1/records/' . $record->get_sbas_id() . '/' . $record->get_record_id() . '/setcollection/'; @@ -1128,7 +1156,7 @@ abstract class ApiTestCase extends \PhraseanetWebTestCase public function testSearchBaskets() { - $this->setToken(self::$DI['oauth2-app-acc-user']->getOauthToken()->getOauthToken()); + $this->setToken($this->adminAccessToken); $route = '/api/v1/baskets/list/'; $this->evaluateMethodNotAllowedRoute($route, ['POST', 'PUT', 'DELETE']); @@ -1146,7 +1174,7 @@ abstract class ApiTestCase extends \PhraseanetWebTestCase public function testAddBasket() { - $this->setToken(self::$DI['oauth2-app-acc-user-not-admin']->getOauthToken()->getOauthToken()); + $this->setToken($this->userAccessToken); $route = '/api/v1/baskets/add/'; @@ -1166,7 +1194,7 @@ abstract class ApiTestCase extends \PhraseanetWebTestCase public function testBasketContent() { - $this->setToken(self::$DI['oauth2-app-acc-user']->getOauthToken()->getOauthToken()); + $this->setToken($this->adminAccessToken); $basketElement = self::$DI['app']['EM']->find('Phraseanet:BasketElement', 1); $basket = $basketElement->getBasket(); @@ -1201,7 +1229,7 @@ abstract class ApiTestCase extends \PhraseanetWebTestCase public function testSetBasketTitle() { - $this->setToken(self::$DI['oauth2-app-acc-user']->getOauthToken()->getOauthToken()); + $this->setToken($this->adminAccessToken); $basket = self::$DI['app']['EM']->find('Phraseanet:Basket', 1); @@ -1249,7 +1277,7 @@ abstract class ApiTestCase extends \PhraseanetWebTestCase public function testSetBasketDescription() { - $this->setToken(self::$DI['oauth2-app-acc-user']->getOauthToken()->getOauthToken()); + $this->setToken($this->adminAccessToken); $basket = self::$DI['app']['EM']->find('Phraseanet:Basket', 1); @@ -1272,7 +1300,7 @@ abstract class ApiTestCase extends \PhraseanetWebTestCase public function testDeleteBasket() { - $this->setToken(self::$DI['oauth2-app-acc-user']->getOauthToken()->getOauthToken()); + $this->setToken($this->adminAccessToken); $route = '/api/v1/baskets/1/delete/'; $this->evaluateMethodNotAllowedRoute($route, ['GET', 'PUT', 'DELETE']); @@ -1298,7 +1326,7 @@ abstract class ApiTestCase extends \PhraseanetWebTestCase public function testAddRecord() { self::$DI['app']['phraseanet.SE'] = $this->createSearchEngineMock(); - $this->setToken(self::$DI['oauth2-app-acc-user-not-admin']->getOauthToken()->getOauthToken()); + $this->setToken($this->userAccessToken); $route = '/api/v1/records/add/'; $params = $this->getAddRecordParameters(); @@ -1318,7 +1346,7 @@ abstract class ApiTestCase extends \PhraseanetWebTestCase public function testAddRecordForceRecord() { self::$DI['app']['phraseanet.SE'] = $this->createSearchEngineMock(); - $this->setToken(self::$DI['oauth2-app-acc-user-not-admin']->getOauthToken()->getOauthToken()); + $this->setToken($this->userAccessToken); $route = '/api/v1/records/add/'; $params = $this->getAddRecordParameters(); @@ -1343,7 +1371,7 @@ abstract class ApiTestCase extends \PhraseanetWebTestCase public function testAddRecordForceLazaret() { - $this->setToken(self::$DI['oauth2-app-acc-user-not-admin']->getOauthToken()->getOauthToken()); + $this->setToken($this->userAccessToken); $route = '/api/v1/records/add/'; $params = $this->getAddRecordParameters(); @@ -1367,7 +1395,7 @@ abstract class ApiTestCase extends \PhraseanetWebTestCase public function testAddRecordWrongBehavior() { - $this->setToken(self::$DI['oauth2-app-acc-user-not-admin']->getOauthToken()->getOauthToken()); + $this->setToken($this->userAccessToken); $route = '/api/v1/records/add/'; $params = $this->getAddRecordParameters(); @@ -1382,7 +1410,7 @@ abstract class ApiTestCase extends \PhraseanetWebTestCase public function testAddRecordWrongBaseId() { - $this->setToken(self::$DI['oauth2-app-acc-user']->getOauthToken()->getOauthToken()); + $this->setToken($this->adminAccessToken); $route = '/api/v1/records/add/'; $params = $this->getAddRecordParameters(); @@ -1397,7 +1425,7 @@ abstract class ApiTestCase extends \PhraseanetWebTestCase public function testAddRecordNoBaseId() { - $this->setToken(self::$DI['oauth2-app-acc-user-not-admin']->getOauthToken()->getOauthToken()); + $this->setToken($this->userAccessToken); $route = '/api/v1/records/add/'; $params = $this->getAddRecordParameters(); @@ -1412,7 +1440,7 @@ abstract class ApiTestCase extends \PhraseanetWebTestCase public function testAddRecordMultipleFiles() { - $this->setToken(self::$DI['oauth2-app-acc-user-not-admin']->getOauthToken()->getOauthToken()); + $this->setToken($this->userAccessToken); $route = '/api/v1/records/add/'; $file = [ @@ -1429,7 +1457,7 @@ abstract class ApiTestCase extends \PhraseanetWebTestCase public function testAddRecordNofile() { - $this->setToken(self::$DI['oauth2-app-acc-user-not-admin']->getOauthToken()->getOauthToken()); + $this->setToken($this->userAccessToken); $route = '/api/v1/records/add/'; self::$DI['client']->request('POST', $route, $this->getParameters($this->getAddRecordParameters()), [], ['HTTP_Accept' => $this->getAcceptMimeType()]); @@ -1443,7 +1471,7 @@ abstract class ApiTestCase extends \PhraseanetWebTestCase { $created_feed = self::$DI['app']['EM']->find('Phraseanet:Feed', 1); - $this->setToken(self::$DI['oauth2-app-acc-user-not-admin']->getOauthToken()->getOauthToken()); + $this->setToken($this->userAccessToken); $route = '/api/v1/feeds/list/'; $this->evaluateMethodNotAllowedRoute($route, ['POST', 'PUT', 'DELETE']); @@ -1494,7 +1522,7 @@ abstract class ApiTestCase extends \PhraseanetWebTestCase self::$DI['app']['EM']->persist($created_entry); self::$DI['app']['EM']->flush(); - $this->setToken(self::$DI['oauth2-app-acc-user-not-admin']->getOauthToken()->getOauthToken()); + $this->setToken($this->userAccessToken); $route = '/api/v1/feeds/content/'; $this->evaluateMethodNotAllowedRoute($route, ['POST', 'PUT', 'DELETE']); @@ -1539,7 +1567,7 @@ abstract class ApiTestCase extends \PhraseanetWebTestCase $feed = self::$DI['app']['EM']->find('Phraseanet:Feed', 1); $created_entry = $feed->getEntries()->first(); - $this->setToken(self::$DI['oauth2-app-acc-user-not-admin']->getOauthToken()->getOauthToken()); + $this->setToken($this->userAccessToken); $route = '/api/v1/feeds/entry/' . $created_entry->getId() . '/'; $this->evaluateMethodNotAllowedRoute($route, ['POST', 'PUT', 'DELETE']); @@ -1568,7 +1596,7 @@ abstract class ApiTestCase extends \PhraseanetWebTestCase $created_feed->setCollection(self::$DI['collection_no_access']); - $this->setToken(self::$DI['oauth2-app-acc-user']->getOauthToken()->getOauthToken()); + $this->setToken($this->adminAccessToken); $route = '/api/v1/feeds/entry/' . $created_entry->getId() . '/'; $this->evaluateMethodNotAllowedRoute($route, ['POST', 'PUT', 'DELETE']); @@ -1596,7 +1624,7 @@ abstract class ApiTestCase extends \PhraseanetWebTestCase self::$DI['app']['EM']->persist($created_entry); self::$DI['app']['EM']->flush(); - $this->setToken(self::$DI['oauth2-app-acc-user-not-admin']->getOauthToken()->getOauthToken()); + $this->setToken($this->userAccessToken); $route = '/api/v1/feeds/' . $created_feed->getId() . '/content/'; $this->evaluateMethodNotAllowedRoute($route, ['POST', 'PUT', 'DELETE']); @@ -1632,7 +1660,7 @@ abstract class ApiTestCase extends \PhraseanetWebTestCase public function testQuarantineList() { - $this->setToken(self::$DI['oauth2-app-acc-user-not-admin']->getOauthToken()->getOauthToken()); + $this->setToken($this->userAccessToken); $route = '/api/v1/quarantine/list/'; $quarantineItemId = self::$DI['lazaret_1']->getId(); @@ -1663,7 +1691,7 @@ abstract class ApiTestCase extends \PhraseanetWebTestCase public function testQuarantineContent() { - $this->setToken(self::$DI['oauth2-app-acc-user-not-admin']->getOauthToken()->getOauthToken()); + $this->setToken($this->userAccessToken); $quarantineItemId = self::$DI['lazaret_1']->getId(); $route = '/api/v1/quarantine/item/' . $quarantineItemId . '/'; @@ -1708,7 +1736,7 @@ abstract class ApiTestCase extends \PhraseanetWebTestCase public function testRouteMe() { - $this->setToken(self::$DI['oauth2-app-acc-user-not-admin']->getOauthToken()->getOauthToken()); + $this->setToken($this->userAccessToken); $route = '/api/v1/me/'; From 46124df500d512b869288185e4245acb8726ff3f Mon Sep 17 00:00:00 2001 From: Nicolas Le Goff Date: Wed, 12 Mar 2014 13:45:04 +0100 Subject: [PATCH 092/112] Remove unique index on account_id --- lib/Alchemy/Phrasea/Model/Entities/ApiOauthToken.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/Alchemy/Phrasea/Model/Entities/ApiOauthToken.php b/lib/Alchemy/Phrasea/Model/Entities/ApiOauthToken.php index 59e1d83bee..16a1842520 100644 --- a/lib/Alchemy/Phrasea/Model/Entities/ApiOauthToken.php +++ b/lib/Alchemy/Phrasea/Model/Entities/ApiOauthToken.php @@ -6,7 +6,10 @@ use Doctrine\ORM\Mapping as ORM; use Gedmo\Mapping\Annotation as Gedmo; /** - * @ORM\Table(name="ApiOauthTokens", indexes={@ORM\Index(name="account_id", columns={"account_id"}), @ORM\Index(name="session_id", columns={"session_id"})}) + * @ORM\Table(name="ApiOauthTokens", indexes={ + * @ORM\Index(name="account_id", columns={"account_id"}), + * @ORM\Index(name="session_id", columns={"session_id"}) + * }) * @ORM\Entity(repositoryClass="Alchemy\Phrasea\Model\Repositories\ApiOauthTokenRepository") */ class ApiOauthToken @@ -25,7 +28,7 @@ class ApiOauthToken private $sessionId; /** - * @ORM\OneToOne(targetEntity="ApiAccount", mappedBy="oauthToken") + * @ORM\ManyToOne(targetEntity="ApiAccount") * @ORM\JoinColumn(name="account_id", referencedColumnName="id", nullable=false) * * @return ApiAccount From e0d538c2c9f712ea635090209c8f26d7270864b5 Mon Sep 17 00:00:00 2001 From: Nicolas Le Goff Date: Wed, 12 Mar 2014 14:03:26 +0100 Subject: [PATCH 093/112] Split route field value & sets expie date to null --- lib/classes/patch/390alpha17a.php | 38 +++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/lib/classes/patch/390alpha17a.php b/lib/classes/patch/390alpha17a.php index 294aaaa534..e5debd8843 100644 --- a/lib/classes/patch/390alpha17a.php +++ b/lib/classes/patch/390alpha17a.php @@ -63,6 +63,8 @@ class patch_390alpha17a extends patchAbstract $this->fillCodeTable($app['EM']); $this->fillRefreshTokenTable($app['EM']); $this->fillOauthTokenTable($app['EM']); + $this->setOauthTokenExpiresToNull($app['EM']); + $this->updateLogsTable($app['EM']); } private function fillApplicationTable(EntityManager $em) @@ -202,4 +204,40 @@ class patch_390alpha17a extends patchAbstract )' ); } + + private function setOauthTokenExpiresToNull(EntityManager $em) + { + $qb = $em->createQueryBuilder(); + $q = $qb->update('Phraseanet:ApiOauthToken', 'a') + ->set('a.expires', $qb->expr()->literal(null)) + ->getQuery(); + $q->execute(); + } + + /** + * Update ApiLogs Table + * + * before : + * +--------------------+ + * | route | + * +--------------------+ + * | GET /databox/list/ | + * +--------------------+ + * + * after : + * +----------------+--------+ + * | route | method | + * +----------------+--------+ + * | /databox/list/ | GET | + * +----------------+--------+ + * + */ + private function updateLogsTable(EntityManager $em) + { + $em->getConnection()->executeUpdate(" + UPDATE `ApiLogs` + SET method = SUBSTRING_INDEX(SUBSTRING_INDEX(route, ' ', 1), ' ', -1), + route = SUBSTRING_INDEX(SUBSTRING_INDEX(route, ' ', 2), ' ', -1) + "); + } } From f327d5a4593d4e7b9290d50ce25b16cc866f25a4 Mon Sep 17 00:00:00 2001 From: Nicolas Le Goff Date: Wed, 12 Mar 2014 14:08:54 +0100 Subject: [PATCH 094/112] Update migration --- .../Phrasea/Setup/DoctrineMigrations/ApiMigration.php | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/lib/Alchemy/Phrasea/Setup/DoctrineMigrations/ApiMigration.php b/lib/Alchemy/Phrasea/Setup/DoctrineMigrations/ApiMigration.php index 737fdd6c1c..b300662695 100644 --- a/lib/Alchemy/Phrasea/Setup/DoctrineMigrations/ApiMigration.php +++ b/lib/Alchemy/Phrasea/Setup/DoctrineMigrations/ApiMigration.php @@ -26,8 +26,8 @@ class ApiMigration extends AbstractMigration $this->addSql("CREATE TABLE ApiApplications (id INT AUTO_INCREMENT NOT NULL, creator_id INT DEFAULT NULL, type VARCHAR(128) NOT NULL, name VARCHAR(128) NOT NULL, description LONGTEXT NOT NULL, website VARCHAR(128) NOT NULL, created DATETIME NOT NULL, updated DATETIME NOT NULL, client_id VARCHAR(32) NOT NULL, client_secret VARCHAR(32) NOT NULL, nonce VARCHAR(64) NOT NULL, redirect_uri VARCHAR(128) NOT NULL, activated TINYINT(1) NOT NULL, grant_password TINYINT(1) NOT NULL, INDEX IDX_53F7BBE661220EA6 (creator_id), UNIQUE INDEX client_id (client_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB"); $this->addSql("CREATE TABLE ApiOauthCodes (code VARCHAR(128) NOT NULL, account_id INT NOT NULL, redirect_uri VARCHAR(128) NOT NULL, expires DATETIME DEFAULT NULL, scope VARCHAR(128) DEFAULT NULL, created DATETIME NOT NULL, updated DATETIME NOT NULL, INDEX IDX_BE6B11809B6B5FBA (account_id), PRIMARY KEY(code)) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB"); $this->addSql("CREATE TABLE ApiOauthRefreshTokens (refresh_token VARCHAR(128) NOT NULL, account_id INT NOT NULL, expires DATETIME NOT NULL, scope VARCHAR(128) DEFAULT NULL, created DATETIME NOT NULL, updated DATETIME NOT NULL, INDEX IDX_7DA42A5A9B6B5FBA (account_id), PRIMARY KEY(refresh_token)) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB"); - $this->addSql("CREATE TABLE ApiOauthTokens (oauth_token VARCHAR(128) NOT NULL, account_id INT NOT NULL, session_id INT DEFAULT NULL, expires DATETIME DEFAULT NULL, scope VARCHAR(128) DEFAULT NULL, created DATETIME NOT NULL, updated DATETIME NOT NULL, UNIQUE INDEX UNIQ_4FD469539B6B5FBA (account_id), INDEX session_id (session_id), PRIMARY KEY(oauth_token)) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB"); - $this->addSql("CREATE TABLE ApiAccounts (id INT AUTO_INCREMENT NOT NULL, user_id INT NOT NULL, application_id INT NOT NULL, oauth_token VARCHAR(32) DEFAULT NULL, revoked TINYINT(1) NOT NULL, api_version VARCHAR(16) NOT NULL, created DATETIME NOT NULL, INDEX IDX_2C54E637A76ED395 (user_id), INDEX IDX_2C54E6373E030ACD (application_id), UNIQUE INDEX UNIQ_2C54E637D8344B2A (oauth_token), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB"); + $this->addSql("CREATE TABLE ApiOauthTokens (oauth_token VARCHAR(128) NOT NULL, account_id INT NOT NULL, session_id INT DEFAULT NULL, expires DATETIME DEFAULT NULL, last_used DATETIME NOT NULL, scope VARCHAR(128) DEFAULT NULL, created DATETIME NOT NULL, updated DATETIME NOT NULL, INDEX IDX_4FD469539B6B5FBA (account_id), INDEX session_id (session_id), PRIMARY KEY(oauth_token)) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB"); + $this->addSql("CREATE TABLE ApiAccounts (id INT AUTO_INCREMENT NOT NULL, user_id INT NOT NULL, application_id INT NOT NULL, revoked TINYINT(1) NOT NULL, api_version VARCHAR(16) NOT NULL, created DATETIME NOT NULL, INDEX IDX_2C54E637A76ED395 (user_id), INDEX IDX_2C54E6373E030ACD (application_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB"); $this->addSql("ALTER TABLE ApiLogs ADD CONSTRAINT FK_91E90F309B6B5FBA FOREIGN KEY (account_id) REFERENCES ApiAccounts (id)"); $this->addSql("ALTER TABLE ApiApplications ADD CONSTRAINT FK_53F7BBE661220EA6 FOREIGN KEY (creator_id) REFERENCES Users (id)"); $this->addSql("ALTER TABLE ApiOauthCodes ADD CONSTRAINT FK_BE6B11809B6B5FBA FOREIGN KEY (account_id) REFERENCES ApiAccounts (id)"); @@ -35,13 +35,11 @@ class ApiMigration extends AbstractMigration $this->addSql("ALTER TABLE ApiOauthTokens ADD CONSTRAINT FK_4FD469539B6B5FBA FOREIGN KEY (account_id) REFERENCES ApiAccounts (id)"); $this->addSql("ALTER TABLE ApiAccounts ADD CONSTRAINT FK_2C54E637A76ED395 FOREIGN KEY (user_id) REFERENCES Users (id)"); $this->addSql("ALTER TABLE ApiAccounts ADD CONSTRAINT FK_2C54E6373E030ACD FOREIGN KEY (application_id) REFERENCES ApiApplications (id)"); - $this->addSql("ALTER TABLE ApiAccounts ADD CONSTRAINT FK_2C54E637D8344B2A FOREIGN KEY (oauth_token) REFERENCES ApiOauthTokens (oauth_token)"); } public function doDownSql(Schema $schema) { $this->addSql("ALTER TABLE ApiAccounts DROP FOREIGN KEY FK_2C54E6373E030ACD"); - $this->addSql("ALTER TABLE ApiAccounts DROP FOREIGN KEY FK_2C54E637D8344B2A"); $this->addSql("ALTER TABLE ApiLogs DROP FOREIGN KEY FK_91E90F309B6B5FBA"); $this->addSql("ALTER TABLE ApiOauthCodes DROP FOREIGN KEY FK_BE6B11809B6B5FBA"); $this->addSql("ALTER TABLE ApiOauthRefreshTokens DROP FOREIGN KEY FK_7DA42A5A9B6B5FBA"); From 89262441536166307f62e13fb39bac8d45d5bc1c Mon Sep 17 00:00:00 2001 From: Nicolas Le Goff Date: Wed, 12 Mar 2014 14:54:10 +0100 Subject: [PATCH 095/112] Fix typo issue --- lib/Alchemy/Phrasea/Controller/Api/Oauth2.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Alchemy/Phrasea/Controller/Api/Oauth2.php b/lib/Alchemy/Phrasea/Controller/Api/Oauth2.php index 58a971eb1d..2f34b02e79 100644 --- a/lib/Alchemy/Phrasea/Controller/Api/Oauth2.php +++ b/lib/Alchemy/Phrasea/Controller/Api/Oauth2.php @@ -99,7 +99,7 @@ class Oauth2 implements ControllerProviderInterface } //check if current client is already authorized by current user - $clients = $app['repo.api-applications']-findAuthorizedAppsByUser($app['authentication']->getUser()); + $clients = $app['repo.api-applications']->findAuthorizedAppsByUser($app['authentication']->getUser()); foreach ($clients as $authClient) { if ($client->getClientId() == $authClient->getClientId()) { From 2ed65f24614eb5e3627ca12afe678ebbdc83edf1 Mon Sep 17 00:00:00 2001 From: Nicolas Le Goff Date: Wed, 12 Mar 2014 14:54:41 +0100 Subject: [PATCH 096/112] Expires can be null --- lib/Alchemy/Phrasea/Model/Entities/ApiOauthCode.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Alchemy/Phrasea/Model/Entities/ApiOauthCode.php b/lib/Alchemy/Phrasea/Model/Entities/ApiOauthCode.php index 4e9f7d2f92..073fa1db7c 100644 --- a/lib/Alchemy/Phrasea/Model/Entities/ApiOauthCode.php +++ b/lib/Alchemy/Phrasea/Model/Entities/ApiOauthCode.php @@ -125,7 +125,7 @@ class ApiOauthCode * * @return ApiOauthCode */ - public function setExpires(\DateTime $expires) + public function setExpires(\DateTime $expires = null) { $this->expires = $expires; From 448baa848468d200bd90d49673e2fc9b63ace13a Mon Sep 17 00:00:00 2001 From: Nicolas Le Goff Date: Wed, 12 Mar 2014 14:54:57 +0100 Subject: [PATCH 097/112] Checks id url is legal --- .../Manipulator/ApiOauthCodeManipulator.php | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/lib/Alchemy/Phrasea/Model/Manipulator/ApiOauthCodeManipulator.php b/lib/Alchemy/Phrasea/Model/Manipulator/ApiOauthCodeManipulator.php index 43022679c6..12b84012f9 100644 --- a/lib/Alchemy/Phrasea/Model/Manipulator/ApiOauthCodeManipulator.php +++ b/lib/Alchemy/Phrasea/Model/Manipulator/ApiOauthCodeManipulator.php @@ -39,7 +39,7 @@ class ApiOauthCodeManipulator implements ManipulatorInterface $code = new ApiOauthCode(); $code->setCode($this->getNewCode()); - $code->setRedirectUri($redirectUri); + $this->doSetRedirectUri($code, $redirectUri); $code->setAccount($account); $code->setExpires($expire); $code->setScope($scope); @@ -67,6 +67,21 @@ class ApiOauthCodeManipulator implements ManipulatorInterface $this->update($code); } + public function setRedirectUri(ApiOauthCode $code, $uri) + { + $this->doSetRedirectUri($code, $uri); + $this->update($code); + } + + private function doSetRedirectUri(ApiOauthCode $code, $uri) + { + if (false === filter_var($uri, FILTER_VALIDATE_URL, FILTER_FLAG_SCHEME_REQUIRED | FILTER_FLAG_HOST_REQUIRED)) { + throw new InvalidArgumentException(sprintf('Redirect Uri Url %s is not legal.', $uri)); + } + + $code->setRedirectUri($uri); + } + private function getNewCode() { do { From 40ff8968e959b24f2a77194bfce34d24094b3df1 Mon Sep 17 00:00:00 2001 From: Nicolas Le Goff Date: Wed, 12 Mar 2014 14:56:20 +0100 Subject: [PATCH 098/112] Raise bad request exception if uri is malformed && Fix use statement --- lib/classes/API/OAuth2/Adapter.php | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/classes/API/OAuth2/Adapter.php b/lib/classes/API/OAuth2/Adapter.php index 48000673c1..9e556cfc6b 100644 --- a/lib/classes/API/OAuth2/Adapter.php +++ b/lib/classes/API/OAuth2/Adapter.php @@ -15,8 +15,10 @@ use Alchemy\Phrasea\Authentication\Exception\AccountLockedException; use Alchemy\Phrasea\Authentication\Exception\RequireCaptchaException; use Alchemy\Phrasea\Exception\RuntimeException; use Alchemy\Phrasea\Model\Entities\ApiApplication; +use Alchemy\Phrasea\Model\Entities\User; use Symfony\Component\HttpFoundation\Session\Session; use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; class API_OAuth2_Adapter extends OAuth2 { @@ -177,7 +179,7 @@ class API_OAuth2_Adapter extends OAuth2 protected function getRedirectUri($clientId) { if (null === $application = $this->app['repo.api-applications']->findByClientId($clientId)) { - throw new RuntimeException(sprintf('Application with client id %s could not be found', $clientId)); + throw new BadRequestHttpException(sprintf('Application with client id %s could not be found', $clientId)); } return $application->getRedirectUri(); @@ -288,13 +290,14 @@ class API_OAuth2_Adapter extends OAuth2 * @return $this|void * @throws RuntimeException */ - protected function setAuthCode($oauthCode, $accountId, $redirectUri, $expires, $scope = null) + protected function setAuthCode($oauthCode, $accountId, $redirectUri, $expires = null, $scope = null) { if (null === $account = $this->app['repo.api-accounts']->find($accountId)) { throw new RuntimeException(sprintf('Account with id %s is not valid', $accountId)); } - $code = $this->app['manipulator.api-oauth-code']->create($account, \DateTime::createFromFormat('U', $expires), $scope); + $expires = null !== $expires ? \DateTime::createFromFormat('U', $expires) : null; + $code = $this->app['manipulator.api-oauth-code']->create($account, $redirectUri, $expires, $scope); $this->app['manipulator.api-oauth-code']->setCode($code, $oauthCode); return $this; From fa9189f78af5294cc9187bdc41fff36e86d98708 Mon Sep 17 00:00:00 2001 From: Nicolas Le Goff Date: Wed, 12 Mar 2014 15:04:49 +0100 Subject: [PATCH 099/112] Add oauth2 adapter as a service --- lib/Alchemy/Phrasea/Application.php | 2 ++ lib/Alchemy/Phrasea/Controller/Api/Oauth2.php | 8 ++--- lib/Alchemy/Phrasea/Controller/Api/V1.php | 7 ++--- .../Core/Provider/APIServiceProvider.php | 29 +++++++++++++++++++ lib/classes/API/OAuth2/Adapter.php | 4 +-- 5 files changed, 38 insertions(+), 12 deletions(-) create mode 100644 lib/Alchemy/Phrasea/Core/Provider/APIServiceProvider.php diff --git a/lib/Alchemy/Phrasea/Application.php b/lib/Alchemy/Phrasea/Application.php index c8724f6b20..8260d1d572 100644 --- a/lib/Alchemy/Phrasea/Application.php +++ b/lib/Alchemy/Phrasea/Application.php @@ -82,6 +82,7 @@ use Alchemy\Phrasea\Core\Middleware\ApiApplicationMiddlewareProvider; use Alchemy\Phrasea\Core\Middleware\BasketMiddlewareProvider; use Alchemy\Phrasea\Core\Middleware\TokenMiddlewareProvider; use Alchemy\Phrasea\Core\Provider\ACLServiceProvider; +use Alchemy\Phrasea\Core\Provider\APIServiceProvider; use Alchemy\Phrasea\Core\Provider\AuthenticationManagerServiceProvider; use Alchemy\Phrasea\Core\Provider\BrowserServiceProvider; use Alchemy\Phrasea\Core\Provider\BorderManagerServiceProvider; @@ -218,6 +219,7 @@ class Application extends SilexApplication $this->register(new ApiApplicationMiddlewareProvider()); $this->register(new ACLServiceProvider()); + $this->register(new APIServiceProvider()); $this->register(new AuthenticationManagerServiceProvider()); $this->register(new BorderManagerServiceProvider()); $this->register(new BrowserServiceProvider()); diff --git a/lib/Alchemy/Phrasea/Controller/Api/Oauth2.php b/lib/Alchemy/Phrasea/Controller/Api/Oauth2.php index 2f34b02e79..f2923a4ce0 100644 --- a/lib/Alchemy/Phrasea/Controller/Api/Oauth2.php +++ b/lib/Alchemy/Phrasea/Controller/Api/Oauth2.php @@ -31,10 +31,6 @@ class Oauth2 implements ControllerProviderInterface $controllers = $app['controllers_factory']; - $app['oauth'] = $app->share(function ($app) { - return new \API_OAuth2_Adapter($app); - }); - /** * AUTHORIZE ENDPOINT * @@ -43,7 +39,7 @@ class Oauth2 implements ControllerProviderInterface */ $authorize_func = function () use ($app) { $request = $app['request']; - $oauth2Adapter = $app['oauth']; + $oauth2Adapter = $app['oauth2-server']; $context = new Context(Context::CONTEXT_OAUTH2_NATIVE); $app['dispatcher']->dispatch(PhraseaEvents::PRE_AUTHENTICATE, new PreAuthenticate($request, $context)); @@ -156,7 +152,7 @@ class Oauth2 implements ControllerProviderInterface throw new HttpException(400, 'This route requires the use of the https scheme', null, ['content-type' => 'application/json']); } - $app['oauth']->grantAccessToken($request); + $app['oauth2-server']->grantAccessToken($request); ob_flush(); flush(); diff --git a/lib/Alchemy/Phrasea/Controller/Api/V1.php b/lib/Alchemy/Phrasea/Controller/Api/V1.php index bd80a1a082..458785d16f 100644 --- a/lib/Alchemy/Phrasea/Controller/Api/V1.php +++ b/lib/Alchemy/Phrasea/Controller/Api/V1.php @@ -1971,10 +1971,9 @@ class V1 implements ControllerProviderInterface $app['dispatcher']->dispatch(PhraseaEvents::PRE_AUTHENTICATE, new PreAuthenticate($request, $context)); $app['dispatcher']->dispatch(PhraseaEvents::API_OAUTH2_START, new ApiOAuth2StartEvent()); - $oauth2_adapter = new \API_OAuth2_Adapter($app); - $oauth2_adapter->verifyAccessToken(); + $app['oauth2-server']->verifyAccessToken(); - if (null === $token = $app['repo.api-oauth-tokens']->find($oauth2_adapter->getToken())) { + if (null === $token = $app['repo.api-oauth-tokens']->find($app['oauth2-server']->getToken())) { throw new NotFoundHttpException('Provided token is not valid.'); } $app['session']->set('token', $token); @@ -1993,7 +1992,7 @@ class V1 implements ControllerProviderInterface } $app['authentication']->openAccount($oAuth2Account->getUser()); - $oauth2_adapter->rememberSession($app['session']); + $app['oauth2-server']->rememberSession($app['session']); $app['dispatcher']->dispatch(PhraseaEvents::API_OAUTH2_END, new ApiOAuth2EndEvent()); } diff --git a/lib/Alchemy/Phrasea/Core/Provider/APIServiceProvider.php b/lib/Alchemy/Phrasea/Core/Provider/APIServiceProvider.php new file mode 100644 index 0000000000..6c65d2fa1e --- /dev/null +++ b/lib/Alchemy/Phrasea/Core/Provider/APIServiceProvider.php @@ -0,0 +1,29 @@ +share(function ($app) { + return new \API_OAuth2_Adapter($app); + }); + } + + public function boot(Application $app) + { + } +} diff --git a/lib/classes/API/OAuth2/Adapter.php b/lib/classes/API/OAuth2/Adapter.php index 9e556cfc6b..b5da9a0b08 100644 --- a/lib/classes/API/OAuth2/Adapter.php +++ b/lib/classes/API/OAuth2/Adapter.php @@ -80,9 +80,9 @@ class API_OAuth2_Adapter extends OAuth2 * @param Application $app * @return API_OAuth2_Adapter */ - public function __construct(Application $app) + public function __construct(Application $app, array $conf = []) { - parent::__construct(); + parent::__construct($conf); $this->app = $app; $this->params = []; } From dce52c26d80c7216b103ff2b239900c53762305c Mon Sep 17 00:00:00 2001 From: Nicolas Le Goff Date: Wed, 12 Mar 2014 16:01:45 +0100 Subject: [PATCH 100/112] Add proper expire date to oauth code token --- lib/classes/API/OAuth2/Adapter.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/classes/API/OAuth2/Adapter.php b/lib/classes/API/OAuth2/Adapter.php index b5da9a0b08..e84beb3d01 100644 --- a/lib/classes/API/OAuth2/Adapter.php +++ b/lib/classes/API/OAuth2/Adapter.php @@ -297,6 +297,11 @@ class API_OAuth2_Adapter extends OAuth2 } $expires = null !== $expires ? \DateTime::createFromFormat('U', $expires) : null; + // @note stored date time are not UTC ... and expires parameter is a UNIX timestamp which is timezone independent + if ($expires instanceof \DateTime) { + $dtz = new \DateTimeZone(date_default_timezone_get()); + $expires->add(new \DateInterval('PT' . $dtz->getOffset($expires) . 'S')); + } $code = $this->app['manipulator.api-oauth-code']->create($account, $redirectUri, $expires, $scope); $this->app['manipulator.api-oauth-code']->setCode($code, $oauthCode); From 137cacea0b3fb9c7e8e8a226153fef3d4b15df97 Mon Sep 17 00:00:00 2001 From: Nicolas Le Goff Date: Wed, 12 Mar 2014 19:49:32 +0100 Subject: [PATCH 101/112] Fix sets of expiration time --- lib/classes/API/OAuth2/Adapter.php | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/lib/classes/API/OAuth2/Adapter.php b/lib/classes/API/OAuth2/Adapter.php index e84beb3d01..bc74e189cb 100644 --- a/lib/classes/API/OAuth2/Adapter.php +++ b/lib/classes/API/OAuth2/Adapter.php @@ -224,7 +224,12 @@ class API_OAuth2_Adapter extends OAuth2 if (null === $account = $this->app['repo.api-accounts']->find($accountId)) { throw new RuntimeException(sprintf('Account with id %s is not valid', $accountId)); } - $expires = null === $expires ? $expires : \DateTime::createFromFormat('U', $expires); + $expires = null !== $expires ? \DateTime::createFromFormat('U', $expires) : null; + // @note stored date time are not UTC ... and expires parameter is a UNIX timestamp which is timezone independent + if ($expires instanceof \DateTime) { + $dtz = new \DateTimeZone(date_default_timezone_get()); + $expires->add(new \DateInterval('PT' . $dtz->getOffset($expires) . 'S')); + } $token = $this->app['manipulator.api-oauth-token']->create($account, $expires, $scope); $this->app['manipulator.api-oauth-token']->setOauthToken($token, $oauthToken); @@ -775,10 +780,9 @@ class API_OAuth2_Adapter extends OAuth2 $expires = null; if ($this->enable_expire) { $token['expires_in'] = $this->getVariable('access_token_lifetime', OAUTH2_DEFAULT_ACCESS_TOKEN_LIFETIME); - $expires = time() + $this->getVariable('access_token_lifetime', OAUTH2_DEFAULT_ACCESS_TOKEN_LIFETIME); } - $this->setAccessToken($token["access_token"], $accountId, $expires, $scope); + $this->setAccessToken($token["access_token"], $accountId, time() + $this->getVariable('access_token_lifetime', OAUTH2_DEFAULT_ACCESS_TOKEN_LIFETIME), $scope); // Issue a refresh token also, if we support them if (in_array(OAUTH2_GRANT_TYPE_REFRESH_TOKEN, $this->getSupportedGrantTypes())) { From 6b50ee15a75ab65360377d4bc67fd4889cea95df Mon Sep 17 00:00:00 2001 From: Nicolas Le Goff Date: Wed, 12 Mar 2014 20:36:39 +0100 Subject: [PATCH 102/112] Add tests --- .../Manipulator/ApiOauthCodeManipulator.php | 1 + .../Manipulator/ApiAccountManipulatorTest.php | 7 ++- .../ApiApplicationManipulatorTest.php | 10 +++- .../ApiOauthCodeManipulatorTest.php | 59 +++++++++++++++++++ .../ApiOauthTokenManipulatorTest.php | 52 ++++++++++++++++ 5 files changed, 123 insertions(+), 6 deletions(-) create mode 100644 tests/Alchemy/Tests/Phrasea/Model/Manipulator/ApiOauthCodeManipulatorTest.php create mode 100644 tests/Alchemy/Tests/Phrasea/Model/Manipulator/ApiOauthTokenManipulatorTest.php diff --git a/lib/Alchemy/Phrasea/Model/Manipulator/ApiOauthCodeManipulator.php b/lib/Alchemy/Phrasea/Model/Manipulator/ApiOauthCodeManipulator.php index 12b84012f9..b8252202fd 100644 --- a/lib/Alchemy/Phrasea/Model/Manipulator/ApiOauthCodeManipulator.php +++ b/lib/Alchemy/Phrasea/Model/Manipulator/ApiOauthCodeManipulator.php @@ -13,6 +13,7 @@ namespace Alchemy\Phrasea\Model\Manipulator; use Alchemy\Phrasea\Application; use Alchemy\Phrasea\Authentication\ACLProvider; +use Alchemy\Phrasea\Exception\InvalidArgumentException; use Alchemy\Phrasea\Model\Entities\ApiAccount; use Alchemy\Phrasea\Model\Entities\ApiOauthCode; use Alchemy\Phrasea\Model\Entities\User; diff --git a/tests/Alchemy/Tests/Phrasea/Model/Manipulator/ApiAccountManipulatorTest.php b/tests/Alchemy/Tests/Phrasea/Model/Manipulator/ApiAccountManipulatorTest.php index ae4dafb9af..90e7da671f 100644 --- a/tests/Alchemy/Tests/Phrasea/Model/Manipulator/ApiAccountManipulatorTest.php +++ b/tests/Alchemy/Tests/Phrasea/Model/Manipulator/ApiAccountManipulatorTest.php @@ -23,12 +23,13 @@ class ApiAccountManipulatorTest extends \PhraseanetTestCase { $manipulator = new ApiAccountManipulator(self::$DI['app']['EM'], self::$DI['app']['repo.api-accounts']); $account = $manipulator->create(self::$DI['oauth2-app-user'], self::$DI['user']); + $accountMem = clone $account; $countBefore = count(self::$DI['app']['repo.api-accounts']->findAll()); - /** - * @todo Link token and tests if token is deleted too - */ + self::$DI['app']['manipulator.api-oauth-token']->create($account); $manipulator->delete($account); $this->assertGreaterThan(count(self::$DI['app']['repo.api-accounts']->findAll()), $countBefore); + $tokens = self::$DI['app']['repo.api-oauth-tokens']->findOauthTokens($accountMem); + $this->assertEquals(0, count($tokens)); } public function testUpdate() diff --git a/tests/Alchemy/Tests/Phrasea/Model/Manipulator/ApiApplicationManipulatorTest.php b/tests/Alchemy/Tests/Phrasea/Model/Manipulator/ApiApplicationManipulatorTest.php index 167d6577e5..2e6c64977a 100644 --- a/tests/Alchemy/Tests/Phrasea/Model/Manipulator/ApiApplicationManipulatorTest.php +++ b/tests/Alchemy/Tests/Phrasea/Model/Manipulator/ApiApplicationManipulatorTest.php @@ -60,12 +60,16 @@ class ApiApplicationManipulatorTest extends \PhraseanetTestCase 'Desktop application description', 'http://desktop-app-url.net' ); + $applicationSave = clone $application; $countBefore = count(self::$DI['app']['repo.api-applications']->findAll()); - /** - * @todo Link accounts and tokens to application and tests if everything is deleted - */ + $account = self::$DI['app']['manipulator.api-account']->create($application, self::$DI['user']); + self::$DI['app']['manipulator.api-oauth-token']->create($account); $manipulator->delete($application); $this->assertGreaterThan(count(self::$DI['app']['repo.api-applications']->findAll()), $countBefore); + $accounts = self::$DI['app']['repo.api-accounts']->findByUserAndApplication(self::$DI['user'], $applicationSave); + $this->assertEquals(0, count($accounts)); + $tokens = self::$DI['app']['repo.api-oauth-tokens']->findOauthTokens($account); + $this->assertEquals(0, count($tokens)); } public function testUpdate() diff --git a/tests/Alchemy/Tests/Phrasea/Model/Manipulator/ApiOauthCodeManipulatorTest.php b/tests/Alchemy/Tests/Phrasea/Model/Manipulator/ApiOauthCodeManipulatorTest.php new file mode 100644 index 0000000000..faea618fb9 --- /dev/null +++ b/tests/Alchemy/Tests/Phrasea/Model/Manipulator/ApiOauthCodeManipulatorTest.php @@ -0,0 +1,59 @@ +findAll()); + $account = self::$DI['app']['manipulator.api-account']->create(self::$DI['oauth2-app-user'], self::$DI['user']); + $manipulator->create($account, 'http://www.redirect.url'); + $this->assertGreaterThan($nbCodes, count(self::$DI['app']['repo.api-oauth-codes']->findAll())); + } + + public function testDelete() + { + $manipulator = new ApiOauthCodeManipulator(self::$DI['app']['EM'], self::$DI['app']['repo.api-oauth-codes'], self::$DI['app']['random.medium']); + $account = self::$DI['app']['manipulator.api-account']->create(self::$DI['oauth2-app-user'], self::$DI['user']); + $code = $manipulator->create($account, 'http://www.redirect.url'); + $countBefore = count(self::$DI['app']['repo.api-oauth-codes']->findAll()); + $manipulator->delete($code); + $this->assertGreaterThan(count(self::$DI['app']['repo.api-oauth-codes']->findAll()), $countBefore); + } + + public function testUpdate() + { + + $manipulator = new ApiOauthCodeManipulator(self::$DI['app']['EM'], self::$DI['app']['repo.api-oauth-codes'], self::$DI['app']['random.medium']); + $account = self::$DI['app']['manipulator.api-account']->create(self::$DI['oauth2-app-user'], self::$DI['user']); + $code = $manipulator->create($account, 'http://www.redirect.url'); + $code->setExpires(new \DateTime()); + $manipulator->update($code); + $code = self::$DI['app']['repo.api-oauth-codes']->find($code->getCode()); + $this->assertNotNull($code->getExpires()); + } + + /** + * @setExpectedException Alchemy\Phrasea\Exception\InvalidArgumentException + */ + public function testSetRedirectUriBadArgumentException() + { + $manipulator = new ApiOauthCodeManipulator(self::$DI['app']['EM'], self::$DI['app']['repo.api-oauth-codes'], self::$DI['app']['random.medium']); + $account = self::$DI['app']['manipulator.api-account']->create(self::$DI['oauth2-app-user'], self::$DI['user']); + $code = $manipulator->create($account, 'http://www.redirect.url'); + try { + $manipulator->setRedirectUri($code, 'bad-url'); + $this->fail('Invalid argument exception should be raised'); + } catch (InvalidArgumentException $e) { + + } + } +} diff --git a/tests/Alchemy/Tests/Phrasea/Model/Manipulator/ApiOauthTokenManipulatorTest.php b/tests/Alchemy/Tests/Phrasea/Model/Manipulator/ApiOauthTokenManipulatorTest.php new file mode 100644 index 0000000000..6096b6ae66 --- /dev/null +++ b/tests/Alchemy/Tests/Phrasea/Model/Manipulator/ApiOauthTokenManipulatorTest.php @@ -0,0 +1,52 @@ +findAll()); + $account = self::$DI['app']['manipulator.api-account']->create(self::$DI['oauth2-app-user'], self::$DI['user']); + $manipulator->create($account); + $this->assertGreaterThan($nbTokens, count(self::$DI['app']['repo.api-oauth-tokens']->findAll())); + } + + public function testDelete() + { + $manipulator = new ApiOauthTokenManipulator(self::$DI['app']['EM'], self::$DI['app']['repo.api-oauth-tokens'], self::$DI['app']['random.medium']); + $account = self::$DI['app']['manipulator.api-account']->create(self::$DI['oauth2-app-user'], self::$DI['user']); + $token = $manipulator->create($account); + $countBefore = count(self::$DI['app']['repo.api-oauth-tokens']->findAll()); + $manipulator->delete($token); + $this->assertGreaterThan(count(self::$DI['app']['repo.api-oauth-tokens']->findAll()), $countBefore); + } + + public function testUpdate() + { + + $manipulator = new ApiOauthTokenManipulator(self::$DI['app']['EM'], self::$DI['app']['repo.api-oauth-tokens'], self::$DI['app']['random.medium']); + $account = self::$DI['app']['manipulator.api-account']->create(self::$DI['oauth2-app-user'], self::$DI['user']); + $token = $manipulator->create($account); + $token->setSessionId(123456); + $manipulator->update($token); + $token = self::$DI['app']['repo.api-oauth-tokens']->find($token->getOauthToken()); + $this->assertEquals(123456, $token->getSessionId()); + } + + public function testRenew() + { + $manipulator = new ApiOauthTokenManipulator(self::$DI['app']['EM'], self::$DI['app']['repo.api-oauth-tokens'], self::$DI['app']['random.medium']); + $account = self::$DI['app']['manipulator.api-account']->create(self::$DI['oauth2-app-user'], self::$DI['user']); + $token = $manipulator->create($account); + $oauthTokenBefore = $token->getOauthToken(); + $manipulator->renew($token); + $this->assertNotEquals($oauthTokenBefore, $token->getOauthToken()); + } +} From 23eff56046f303462b4d430b93f15f714604f8ac Mon Sep 17 00:00:00 2001 From: Nicolas Le Goff Date: Thu, 13 Mar 2014 15:31:19 +0100 Subject: [PATCH 103/112] Add bi-directional reloationship for application to account & accout to tokens --- .../Phrasea/Model/Entities/ApiAccount.php | 30 ++++++++++++++++++- .../Phrasea/Model/Entities/ApiApplication.php | 15 ++++++++-- .../Phrasea/Model/Entities/ApiOauthToken.php | 4 ++- 3 files changed, 44 insertions(+), 5 deletions(-) diff --git a/lib/Alchemy/Phrasea/Model/Entities/ApiAccount.php b/lib/Alchemy/Phrasea/Model/Entities/ApiAccount.php index a942242e39..4db97031fc 100644 --- a/lib/Alchemy/Phrasea/Model/Entities/ApiAccount.php +++ b/lib/Alchemy/Phrasea/Model/Entities/ApiAccount.php @@ -2,11 +2,15 @@ namespace Alchemy\Phrasea\Model\Entities; +use Doctrine\Common\Collections\ArrayCollection; use Doctrine\ORM\Mapping as ORM; use Gedmo\Mapping\Annotation as Gedmo; /** - * @ORM\Table(name="ApiAccounts", indexes={@ORM\Index(name="user_id", columns={"user_id"}), @ORM\Index(name="application_id", columns={"application_id"})}) + * @ORM\Table(name="ApiAccounts", indexes={ + * @ORM\Index(name="user_id", columns={"user_id"}), + * @ORM\Index(name="application_id", columns={"application_id"}) + * }) * @ORM\Entity(repositoryClass="Alchemy\Phrasea\Model\Repositories\ApiAccountRepository") */ class ApiAccount @@ -48,12 +52,22 @@ class ApiAccount **/ private $application; + /** + * @ORM\OneToMany(targetEntity="ApiOauthToken", mappedBy="account", cascade={"remove"}) + **/ + private $tokens; + /** * @Gedmo\Timestampable(on="create") * @ORM\Column(type="datetime") */ private $created; + public function __construct() + { + $this->tokens = new ArrayCollection(); + } + /** * @param string $apiVersion * @@ -81,6 +95,8 @@ class ApiAccount */ public function setApplication(ApiApplication $application) { + $application->addAccount($this); + $this->application = $application; return $this; @@ -161,4 +177,16 @@ class ApiAccount { return $this->user; } + + /** + * @param ApiOauthToken $token + * + * @return $this + */ + public function addTokens(ApiOauthToken $token) + { + $this->tokens->add($token); + + return $this; + } } diff --git a/lib/Alchemy/Phrasea/Model/Entities/ApiApplication.php b/lib/Alchemy/Phrasea/Model/Entities/ApiApplication.php index ed2506ee43..d2ffca392e 100644 --- a/lib/Alchemy/Phrasea/Model/Entities/ApiApplication.php +++ b/lib/Alchemy/Phrasea/Model/Entities/ApiApplication.php @@ -125,6 +125,11 @@ class ApiApplication **/ private $accounts; + public function __construct() + { + $this->accounts = new ArrayCollection(); + } + /** * @param boolean $activated * @@ -394,10 +399,14 @@ class ApiApplication } /** - * @return ArrayCollection + * @param ApiAccount $account + * + * @return $this */ - public function getAccounts() + public function addAccount(ApiAccount $account) { - return $this->accounts; + $this->accounts->add($account); + + return $this; } } diff --git a/lib/Alchemy/Phrasea/Model/Entities/ApiOauthToken.php b/lib/Alchemy/Phrasea/Model/Entities/ApiOauthToken.php index 16a1842520..32683fe5c7 100644 --- a/lib/Alchemy/Phrasea/Model/Entities/ApiOauthToken.php +++ b/lib/Alchemy/Phrasea/Model/Entities/ApiOauthToken.php @@ -28,7 +28,7 @@ class ApiOauthToken private $sessionId; /** - * @ORM\ManyToOne(targetEntity="ApiAccount") + * @ORM\ManyToOne(targetEntity="ApiAccount", inversedBy="tokens") * @ORM\JoinColumn(name="account_id", referencedColumnName="id", nullable=false) * * @return ApiAccount @@ -74,6 +74,8 @@ class ApiOauthToken */ public function setAccount(ApiAccount $account) { + $account->addTokens($this); + $this->account = $account; return $this; From 16d9b0e61c9f75718b7ccc1affb3769a46904fd5 Mon Sep 17 00:00:00 2001 From: Nicolas Le Goff Date: Thu, 13 Mar 2014 15:31:51 +0100 Subject: [PATCH 104/112] Updates tests --- .../Model/Manipulator/ApiApplicationManipulatorTest.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/Alchemy/Tests/Phrasea/Model/Manipulator/ApiApplicationManipulatorTest.php b/tests/Alchemy/Tests/Phrasea/Model/Manipulator/ApiApplicationManipulatorTest.php index 2e6c64977a..2aeb32f091 100644 --- a/tests/Alchemy/Tests/Phrasea/Model/Manipulator/ApiApplicationManipulatorTest.php +++ b/tests/Alchemy/Tests/Phrasea/Model/Manipulator/ApiApplicationManipulatorTest.php @@ -63,12 +63,13 @@ class ApiApplicationManipulatorTest extends \PhraseanetTestCase $applicationSave = clone $application; $countBefore = count(self::$DI['app']['repo.api-applications']->findAll()); $account = self::$DI['app']['manipulator.api-account']->create($application, self::$DI['user']); + $accountMem = clone $account; self::$DI['app']['manipulator.api-oauth-token']->create($account); $manipulator->delete($application); $this->assertGreaterThan(count(self::$DI['app']['repo.api-applications']->findAll()), $countBefore); $accounts = self::$DI['app']['repo.api-accounts']->findByUserAndApplication(self::$DI['user'], $applicationSave); $this->assertEquals(0, count($accounts)); - $tokens = self::$DI['app']['repo.api-oauth-tokens']->findOauthTokens($account); + $tokens = self::$DI['app']['repo.api-oauth-tokens']->findOauthTokens($accountMem); $this->assertEquals(0, count($tokens)); } From bdde87718c0a21be52b2d99964542136cf7669c5 Mon Sep 17 00:00:00 2001 From: Nicolas Le Goff Date: Thu, 13 Mar 2014 16:50:50 +0100 Subject: [PATCH 105/112] Fix authorize oauth2 application --- www/skins/account/account.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/www/skins/account/account.js b/www/skins/account/account.js index 86c2fef1e9..f655f75b78 100644 --- a/www/skins/account/account.js +++ b/www/skins/account/account.js @@ -7,7 +7,7 @@ $(document).ready(function () { type: "GET", url: $this.attr("href"), dataType: 'json', - data: {revoke: $this.hasClass("authorize") ? 1 : 0}, + data: {revoke: $this.hasClass("authorize") ? 0 : 1}, success: function (data) { if (data.success) { var li = $this.closest('li'); From 9de0dd41dcae2ba0395b7eaeec277743d8cd9e13 Mon Sep 17 00:00:00 2001 From: Nicolas Le Goff Date: Thu, 13 Mar 2014 17:15:17 +0100 Subject: [PATCH 106/112] Fix uri detection for oauth code --- .../Phrasea/Model/Manipulator/ApiOauthCodeManipulator.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/Alchemy/Phrasea/Model/Manipulator/ApiOauthCodeManipulator.php b/lib/Alchemy/Phrasea/Model/Manipulator/ApiOauthCodeManipulator.php index b8252202fd..6d73483928 100644 --- a/lib/Alchemy/Phrasea/Model/Manipulator/ApiOauthCodeManipulator.php +++ b/lib/Alchemy/Phrasea/Model/Manipulator/ApiOauthCodeManipulator.php @@ -16,6 +16,7 @@ use Alchemy\Phrasea\Authentication\ACLProvider; use Alchemy\Phrasea\Exception\InvalidArgumentException; use Alchemy\Phrasea\Model\Entities\ApiAccount; use Alchemy\Phrasea\Model\Entities\ApiOauthCode; +use Alchemy\Phrasea\Model\Entities\ApiApplication; use Alchemy\Phrasea\Model\Entities\User; use Alchemy\Phrasea\Model\Manipulator\TokenManipulator; use Doctrine\Common\Persistence\ObjectManager; @@ -76,7 +77,9 @@ class ApiOauthCodeManipulator implements ManipulatorInterface private function doSetRedirectUri(ApiOauthCode $code, $uri) { - if (false === filter_var($uri, FILTER_VALIDATE_URL, FILTER_FLAG_SCHEME_REQUIRED | FILTER_FLAG_HOST_REQUIRED)) { + if (false === filter_var($uri, FILTER_VALIDATE_URL, FILTER_FLAG_SCHEME_REQUIRED | FILTER_FLAG_HOST_REQUIRED) + && $uri !== ApiApplication::NATIVE_APP_REDIRECT_URI + ) { throw new InvalidArgumentException(sprintf('Redirect Uri Url %s is not legal.', $uri)); } From d4f114b13b091353af16b0c177e3fdf4f2d7fa6b Mon Sep 17 00:00:00 2001 From: Nicolas Le Goff Date: Thu, 13 Mar 2014 17:15:34 +0100 Subject: [PATCH 107/112] Fix native app access token display --- templates/web/api/auth/native_app_access_token.html.twig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/web/api/auth/native_app_access_token.html.twig b/templates/web/api/auth/native_app_access_token.html.twig index 9a6b41a04d..1c7245734f 100644 --- a/templates/web/api/auth/native_app_access_token.html.twig +++ b/templates/web/api/auth/native_app_access_token.html.twig @@ -37,7 +37,7 @@

{{ app['conf'].get(['registry', 'general', 'title']) }}

- {% if user is not none %} + {% if app['authentication'].getUser() is not none %} {% set username = '' ~ app['authentication'].getUser().getDisplayName() ~ '' %}