Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add new public key token provider (tokens survive password change) #9485

Merged
merged 13 commits into from
Jun 19, 2018

Conversation

rullzer
Copy link
Member

@rullzer rullzer commented May 15, 2018

For #9441

todo:

  • Finish unit tests

@rullzer rullzer added this to the Nextcloud 14 milestone May 15, 2018
@rullzer rullzer force-pushed the feature/9441/multiple_token_providers branch from 676d76e to 9eb24b7 Compare May 18, 2018 19:09
@codecov
Copy link

codecov bot commented May 18, 2018

Codecov Report

Merging #9485 into master will increase coverage by 0.14%.
The diff coverage is 89.54%.

@@             Coverage Diff              @@
##             master    #9485      +/-   ##
============================================
+ Coverage     51.92%   52.06%   +0.14%     
- Complexity    25808    25901      +93     
============================================
  Files          1637     1642       +5     
  Lines         95513    95872     +359     
  Branches       1318     1318              
============================================
+ Hits          49593    49917     +324     
- Misses        45920    45955      +35
Impacted Files Coverage Δ Complexity Δ
core/Migrations/Version14000Date20180518120534.php 0% <0%> (ø) 1 <1> (?)
version.php 0% <0%> (ø) 0 <0> (ø) ⬇️
...vate/Authentication/Token/DefaultTokenProvider.php 98.05% <100%> (+0.01%) 32 <3> (ø) ⬇️
lib/private/Server.php 82.42% <100%> (-0.14%) 279 <0> (-1)
settings/Controller/AuthSettingsController.php 78.37% <100%> (+0.87%) 18 <0> (-2) ⬇️
...rivate/Authentication/Token/DefaultTokenMapper.php 100% <100%> (ø) 11 <2> (ø) ⬇️
lib/private/Authentication/Token/DefaultToken.php 91.93% <100%> (+6.68%) 20 <0> (ø) ⬇️
lib/private/Authentication/Token/Manager.php 100% <100%> (ø) 24 <24> (?)
...ib/private/Authentication/Token/PublicKeyToken.php 82.81% <82.81%> (ø) 20 <20> (?)
...vate/Authentication/Token/PublicKeyTokenMapper.php 90.54% <90.54%> (ø) 12 <12> (?)
... and 8 more

@rullzer rullzer force-pushed the feature/9441/multiple_token_providers branch from 9eb24b7 to bee7599 Compare May 18, 2018 19:11
@rullzer rullzer force-pushed the feature/9441/multiple_token_providers branch 4 times, most recently from 504a562 to 29c5f86 Compare June 1, 2018 13:34
@rullzer
Copy link
Member Author

rullzer commented Jun 1, 2018

@ChristophWurst a first review would be appreciated :)

@rullzer rullzer force-pushed the feature/9441/multiple_token_providers branch from cca529a to f09b4bb Compare June 1, 2018 19:16
Copy link
Member

@ChristophWurst ChristophWurst left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some early feedback, as requested 😉

@@ -85,6 +89,9 @@ public function __construct() {
$this->addType('lastCheck', 'int');
$this->addType('scope', 'string');
$this->addType('expires', 'int');
$this->addType('version', 'int');

$this->setVersion(1);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this will mark the version field as updated, although it won't actually be updated in many cases. This could cause unnecessary updates

$this->markFieldUpdated($name);

You might want to solve this differently.

->where($qb->expr()->eq('token', $qb->createParameter('token')))
->setParameter('token', $token)
->where($qb->expr()->eq('token', $qb->createNamedParameter($token, IQueryBuilder::PARAM_STR)))
->andWhere($qb->expr()->eq('version', $qb->createNamedParameter(1, IQueryBuilder::PARAM_INT)))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd appreciate using a constant for the version number 😉

* @param int $id
*/
public function invalidateTokenById(IUser $user, int $id);
public function invalidateTokenById(string $uid, int $id);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I usually prefer to work with IUser objects on higher-level services over stringly-typed UIDs. What's the reasoning for choosing strings over rich objects?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. Because we want to update all other tokens when we cdhange a password. I would need to inject the user manager to get the user object. Because the Tokens only have the uid.

* @throws InvalidTokenException
*/
public function updateToken(IToken $token) {
if ($token instanceof DefaultToken) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO it would make the code a lot easier if there was a private method getProvider($token).

Like

$this->getProvider($token)->updateToken($token);

where getProvider automatically throws the InvalidTokenException for unkown token types.

esp. since it's used many times in this class

$this->addType('privateKey', 'string');
$this->addType('version', 'int');

$this->setVersion(2);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same as above, this could be problematic for the dirty checking of the entity/mapper logic

$qb = $this->db->getQueryBuilder();
$qb->delete('authtoken')
->where($qb->expr()->eq('token', $qb->createNamedParameter($token)))
->andWhere($qb->expr()->eq('version', $qb->createNamedParameter(2, IQueryBuilder::PARAM_INT)))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

again, I would appreciate a constant for the token version

}

$password = null;
if (!is_null($token->getPassword())) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh, I though you were in team !== null 🙈

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy paste ;)

throw new InvalidTokenException();
}

// When changeing passwords all temp tokens are deleted
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo

@@ -98,6 +98,7 @@ protected function getConnection() {
$options['signature_version'] = 'v2';
}
$this->connection = new S3Client($options);
$this->connection->getCredentials();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure what you're trying to sneak into our code base here

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good question...

@rullzer rullzer force-pushed the feature/9441/multiple_token_providers branch 4 times, most recently from 21cc5f1 to 5dc8dfd Compare June 4, 2018 09:06
@rullzer rullzer added 3. to review Waiting for reviews and removed 2. developing Work in progress labels Jun 7, 2018
@rullzer
Copy link
Member Author

rullzer commented Jun 7, 2018

This is ready for review and testing.

$provider->updateTokenActivity($token);
}

public function getTokenByUser(string $uid): array {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the mixture of provided phpdoc and missing phpdoc… here it would be useful to doc IToken[]

Copy link
Member

@blizzz blizzz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(tbc)

* @throws InvalidTokenException
* @return IToken
*/
public function getTokenById(int $tokenId): IToken {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

besides by the type, the signature looks the same as in getToken. I guess it is database ID vs. token, um, value? Perhaps we can get the naming more clear in either this method or the upper one?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I don't like it either. But this was basically just copy pasted from the current implementation.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

see, the IProvider is not in public namespace, so … but yeah, 🙈 🏁

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup, it's confusing.

}

public function setScope($scope) {
if (\is_array($scope)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

\ shouldn't be necessary

@rullzer rullzer force-pushed the feature/9441/multiple_token_providers branch from 5dc8dfd to dd83bc5 Compare June 14, 2018 17:39
@rullzer
Copy link
Member Author

rullzer commented Jun 14, 2018

@blizzz addressed most. Lets get this in :)

Copy link
Member

@blizzz blizzz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just some 💄 remarks, looks good and works otherwise 👍

$secret = $this->config->getSystemValue('secret');
try {
return $this->crypto->decrypt($cipherText, $token . $secret);
} catch (\Exception $ex) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is it not tooo broad? without having checked what else might come upon decrypt

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just want to catch it all. Better to throw an invalid token exception in the login and have it fail then do unexpected stuff.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok

* @param IUser $user
* @return IToken[]
*/
public function getTokenByUser(IUser $user): array {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also remove \OCP\IUser use statement


use OC\Authentication\Exceptions\InvalidTokenException;
use OC\Authentication\Exceptions\PasswordlessTokenException;
use OCP\IUser;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔥

$this->publicKeyTokenProvider->invalidateOldTokens();
}

public function rotate(IToken $token, string $oldTokenId, string $newTokenId): IToken {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@throws annotations would be nice

if ($token instanceof PublicKeyToken) {
return $this->publicKeyTokenProvider;
}
throw new InvalidTokenException();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

annotate? would make IDE more calm ;)

use OCP\AppFramework\Utility\ITimeFactory;
use OCP\IConfig;
use OCP\ILogger;
use OCP\IUser;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔥

* where a high number of (session) tokens is generated
*
* @param string $uid
* @return DefaultToken[]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PublicKeyToken[]

Signed-off-by: Roeland Jago Douma <roeland@famdouma.nl>
Signed-off-by: Roeland Jago Douma <roeland@famdouma.nl>
Signed-off-by: Roeland Jago Douma <roeland@famdouma.nl>
Signed-off-by: Roeland Jago Douma <roeland@famdouma.nl>
We don't have user objects in the code everywhere

Signed-off-by: Roeland Jago Douma <roeland@famdouma.nl>
Signed-off-by: Roeland Jago Douma <roeland@famdouma.nl>
Signed-off-by: Roeland Jago Douma <roeland@famdouma.nl>
* When getting the token
* When rotating the token

* Also store the encrypted password as base64 to avoid weird binary
stuff

Signed-off-by: Roeland Jago Douma <roeland@famdouma.nl>
Signed-off-by: Roeland Jago Douma <roeland@famdouma.nl>
* Add a lot of tests
* Fixes related to those tests
* Fix tests

Signed-off-by: Roeland Jago Douma <roeland@famdouma.nl>
And don't set the version in the constructor. That would possible cause
to many updates.

Signed-off-by: Roeland Jago Douma <roeland@famdouma.nl>
Signed-off-by: Roeland Jago Douma <roeland@famdouma.nl>
Signed-off-by: Roeland Jago Douma <roeland@famdouma.nl>
@rullzer rullzer force-pushed the feature/9441/multiple_token_providers branch from dd83bc5 to 82959ca Compare June 19, 2018 05:46
Copy link
Member

@ChristophWurst ChristophWurst left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code looks good!

/** @var IConfig */
private $config;

/** @var ILogger $logger */
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: $logger seems to be unnecessary, ie the doc comments are different to the ones above

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not worth to wait another iteration, let's just get this out as by catch one next pr

$pkToken->setExpires($defaultToken->getExpires());
$pkToken->setId($defaultToken->getId());

return $this->mapper->update($pkToken);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seemed weird that you're using update for a new token, but then I realized it's the same table/row 👍

@@ -29,7 +29,7 @@
// between betas, final and RCs. This is _not_ the public version number. Reset minor/patchlevel
// when updating major/minor version number.

$OC_Version = array(14, 0, 0, 4);
$OC_Version = array(14, 0, 0, 5);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@blizzz blizzz merged commit 7ef722e into master Jun 19, 2018
@blizzz blizzz deleted the feature/9441/multiple_token_providers branch June 19, 2018 08:11
@ChristophWurst ChristophWurst mentioned this pull request Jun 19, 2018
8 tasks
@rullzer rullzer mentioned this pull request Jun 19, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants