You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

275 lines
7.9 KiB

3 years ago
  1. <?php
  2. declare(strict_types=1);
  3. /**
  4. * @copyright Copyright 2018, Roeland Jago Douma <roeland@famdouma.nl>
  5. *
  6. * @author Christoph Wurst <christoph@winzerhof-wurst.at>
  7. * @author Morris Jobke <hey@morrisjobke.de>
  8. * @author Roeland Jago Douma <roeland@famdouma.nl>
  9. *
  10. * @license GNU AGPL version 3 or any later version
  11. *
  12. * This program is free software: you can redistribute it and/or modify
  13. * it under the terms of the GNU Affero General Public License as
  14. * published by the Free Software Foundation, either version 3 of the
  15. * License, or (at your option) any later version.
  16. *
  17. * This program is distributed in the hope that it will be useful,
  18. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  19. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  20. * GNU Affero General Public License for more details.
  21. *
  22. * You should have received a copy of the GNU Affero General Public License
  23. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  24. *
  25. */
  26. namespace OC\Authentication\Token;
  27. use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
  28. use OC\Authentication\Exceptions\ExpiredTokenException;
  29. use OC\Authentication\Exceptions\InvalidTokenException;
  30. use OC\Authentication\Exceptions\PasswordlessTokenException;
  31. use OC\Authentication\Exceptions\WipeTokenException;
  32. class Manager implements IProvider {
  33. /** @var DefaultTokenProvider */
  34. private $defaultTokenProvider;
  35. /** @var PublicKeyTokenProvider */
  36. private $publicKeyTokenProvider;
  37. public function __construct(DefaultTokenProvider $defaultTokenProvider, PublicKeyTokenProvider $publicKeyTokenProvider) {
  38. $this->defaultTokenProvider = $defaultTokenProvider;
  39. $this->publicKeyTokenProvider = $publicKeyTokenProvider;
  40. }
  41. /**
  42. * Create and persist a new token
  43. *
  44. * @param string $token
  45. * @param string $uid
  46. * @param string $loginName
  47. * @param string|null $password
  48. * @param string $name
  49. * @param int $type token type
  50. * @param int $remember whether the session token should be used for remember-me
  51. * @return IToken
  52. */
  53. public function generateToken(string $token,
  54. string $uid,
  55. string $loginName,
  56. $password,
  57. string $name,
  58. int $type = IToken::TEMPORARY_TOKEN,
  59. int $remember = IToken::DO_NOT_REMEMBER): IToken {
  60. try {
  61. return $this->publicKeyTokenProvider->generateToken(
  62. $token,
  63. $uid,
  64. $loginName,
  65. $password,
  66. $name,
  67. $type,
  68. $remember
  69. );
  70. } catch (UniqueConstraintViolationException $e) {
  71. // It's rare, but if two requests of the same session (e.g. env-based SAML)
  72. // try to create the session token they might end up here at the same time
  73. // because we use the session ID as token and the db token is created anew
  74. // with every request.
  75. //
  76. // If the UIDs match, then this should be fine.
  77. $existing = $this->getToken($token);
  78. if ($existing->getUID() !== $uid) {
  79. throw new \Exception('Token conflict handled, but UIDs do not match. This should not happen', 0, $e);
  80. }
  81. return $existing;
  82. }
  83. }
  84. /**
  85. * Save the updated token
  86. *
  87. * @param IToken $token
  88. * @throws InvalidTokenException
  89. */
  90. public function updateToken(IToken $token) {
  91. $provider = $this->getProvider($token);
  92. $provider->updateToken($token);
  93. }
  94. /**
  95. * Update token activity timestamp
  96. *
  97. * @throws InvalidTokenException
  98. * @param IToken $token
  99. */
  100. public function updateTokenActivity(IToken $token) {
  101. $provider = $this->getProvider($token);
  102. $provider->updateTokenActivity($token);
  103. }
  104. /**
  105. * @param string $uid
  106. * @return IToken[]
  107. */
  108. public function getTokenByUser(string $uid): array {
  109. $old = $this->defaultTokenProvider->getTokenByUser($uid);
  110. $new = $this->publicKeyTokenProvider->getTokenByUser($uid);
  111. return array_merge($old, $new);
  112. }
  113. /**
  114. * Get a token by token
  115. *
  116. * @param string $tokenId
  117. * @throws InvalidTokenException
  118. * @throws \RuntimeException when OpenSSL reports a problem
  119. * @return IToken
  120. */
  121. public function getToken(string $tokenId): IToken {
  122. try {
  123. return $this->publicKeyTokenProvider->getToken($tokenId);
  124. } catch (WipeTokenException $e) {
  125. throw $e;
  126. } catch (ExpiredTokenException $e) {
  127. throw $e;
  128. } catch (InvalidTokenException $e) {
  129. // No worries we try to convert it to a PublicKey Token
  130. }
  131. //Convert!
  132. $token = $this->defaultTokenProvider->getToken($tokenId);
  133. try {
  134. $password = $this->defaultTokenProvider->getPassword($token, $tokenId);
  135. } catch (PasswordlessTokenException $e) {
  136. $password = null;
  137. }
  138. return $this->publicKeyTokenProvider->convertToken($token, $tokenId, $password);
  139. }
  140. /**
  141. * Get a token by token id
  142. *
  143. * @param int $tokenId
  144. * @throws InvalidTokenException
  145. * @return IToken
  146. */
  147. public function getTokenById(int $tokenId): IToken {
  148. try {
  149. return $this->publicKeyTokenProvider->getTokenById($tokenId);
  150. } catch (ExpiredTokenException $e) {
  151. throw $e;
  152. } catch (WipeTokenException $e) {
  153. throw $e;
  154. } catch (InvalidTokenException $e) {
  155. return $this->defaultTokenProvider->getTokenById($tokenId);
  156. }
  157. }
  158. /**
  159. * @param string $oldSessionId
  160. * @param string $sessionId
  161. * @throws InvalidTokenException
  162. * @return IToken
  163. */
  164. public function renewSessionToken(string $oldSessionId, string $sessionId): IToken {
  165. try {
  166. return $this->publicKeyTokenProvider->renewSessionToken($oldSessionId, $sessionId);
  167. } catch (ExpiredTokenException $e) {
  168. throw $e;
  169. } catch (InvalidTokenException $e) {
  170. return $this->defaultTokenProvider->renewSessionToken($oldSessionId, $sessionId);
  171. }
  172. }
  173. /**
  174. * @param IToken $savedToken
  175. * @param string $tokenId session token
  176. * @throws InvalidTokenException
  177. * @throws PasswordlessTokenException
  178. * @return string
  179. */
  180. public function getPassword(IToken $savedToken, string $tokenId): string {
  181. $provider = $this->getProvider($savedToken);
  182. return $provider->getPassword($savedToken, $tokenId);
  183. }
  184. public function setPassword(IToken $token, string $tokenId, string $password) {
  185. $provider = $this->getProvider($token);
  186. $provider->setPassword($token, $tokenId, $password);
  187. }
  188. public function invalidateToken(string $token) {
  189. $this->defaultTokenProvider->invalidateToken($token);
  190. $this->publicKeyTokenProvider->invalidateToken($token);
  191. }
  192. public function invalidateTokenById(string $uid, int $id) {
  193. $this->defaultTokenProvider->invalidateTokenById($uid, $id);
  194. $this->publicKeyTokenProvider->invalidateTokenById($uid, $id);
  195. }
  196. public function invalidateOldTokens() {
  197. $this->defaultTokenProvider->invalidateOldTokens();
  198. $this->publicKeyTokenProvider->invalidateOldTokens();
  199. }
  200. /**
  201. * @param IToken $token
  202. * @param string $oldTokenId
  203. * @param string $newTokenId
  204. * @return IToken
  205. * @throws InvalidTokenException
  206. * @throws \RuntimeException when OpenSSL reports a problem
  207. */
  208. public function rotate(IToken $token, string $oldTokenId, string $newTokenId): IToken {
  209. if ($token instanceof DefaultToken) {
  210. try {
  211. $password = $this->defaultTokenProvider->getPassword($token, $oldTokenId);
  212. } catch (PasswordlessTokenException $e) {
  213. $password = null;
  214. }
  215. return $this->publicKeyTokenProvider->convertToken($token, $newTokenId, $password);
  216. }
  217. if ($token instanceof PublicKeyToken) {
  218. return $this->publicKeyTokenProvider->rotate($token, $oldTokenId, $newTokenId);
  219. }
  220. throw new InvalidTokenException();
  221. }
  222. /**
  223. * @param IToken $token
  224. * @return IProvider
  225. * @throws InvalidTokenException
  226. */
  227. private function getProvider(IToken $token): IProvider {
  228. if ($token instanceof DefaultToken) {
  229. return $this->defaultTokenProvider;
  230. }
  231. if ($token instanceof PublicKeyToken) {
  232. return $this->publicKeyTokenProvider;
  233. }
  234. throw new InvalidTokenException();
  235. }
  236. public function markPasswordInvalid(IToken $token, string $tokenId) {
  237. $this->getProvider($token)->markPasswordInvalid($token, $tokenId);
  238. }
  239. public function updatePasswords(string $uid, string $password) {
  240. $this->defaultTokenProvider->updatePasswords($uid, $password);
  241. $this->publicKeyTokenProvider->updatePasswords($uid, $password);
  242. }
  243. }