-
Notifications
You must be signed in to change notification settings - Fork 30
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
Auth Token feature #1952
base: master
Are you sure you want to change the base?
Auth Token feature #1952
Conversation
dd6925e
to
636d6ac
Compare
636d6ac
to
b0ed068
Compare
This feature is now ready for review |
Hey Luis, sorry for delay! I am planning to review this one by today/tomorrow.. I'll come back to you asap 🚀 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hey again,
Here a first round of review, overall looks good and looks working. I tried the use case where I try to upload a new run and it looks working using the generated API key.
curl -s --url-query "test=optaplanner-jmh-benchmarks-8x" -X POST -H "content-type: application/json" -H 'X-Horreum-API-Key: <MY_KEY>' 'http://localhost:8080/api/run/data' -d @/tmp/op-run.json
There are still a couple of comments:
- @barreiro could you please rebase this on top of
master
? those changes shouldn't affect this feature but just to be sure. - Return error msg when the API Key validation failed, I tried to run the same upload command with either a fake key or a revoked one and in both cases no error is returned to the user even if the request failed.
Here an example:
If don't send any auth token, the rest will return a generic error msg:
$ curl -s --url-query "test=optaplanner-jmh-benchmarks-8x" --url-query "owner=optaplanner-team" --url-query "start=2024-07-09T09:45:38" --url-query "stop=2024-07-09T09:45:38" -X POST -H "content-type: application/json" 'http://localhost:8080/api/run/data' -d @/tmp/op-run.json
Cannot upload to test optaplanner-jmh-benchmarks-8x%
and in the logs I see (in DEBUG)
DEBUG [io.hyp.too.hor.svc.TestServiceImpl] (executor-thread-1) Failed to retrieve test optaplanner-jmh-benchmarks-8x as this user ( = []) is not uploader for optaplanner-team and token null does not match
If I send, instead, a revoked token:
$ curl -s --url-query "test=optaplanner-jmh-benchmarks-8x" --url-query "owner=optaplanner-team" --url-query "start=2024-07-09T09:45:38" --url-query "stop=2024-07-09T09:45:38" -X POST -H "content-type: application/json" -H 'X-Horreum-API-Key: HUSR_0A1CE383_38D9_4AFB_B094_99B0D6D6BE73' 'http://localhost:8080/api/run/data' -d @/tmp/op-run.json
there is no response at all and no logs as well, I think it could be very useful to return at least an error msg, even a generic "cannot upload to test ..."
I would suggest to show just the button when there is no API keys set similarly to what we are doing in other places - or showing a message like "No API keys set" and below the button (that's actually just a minor).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@barreiro this looks great!
For this PR, we need user docs adding
For future PR's i think we need to open issues to;
- add global management of keys at admin level
- remove "tokens" and "machine accounts" features
public LocalDate creation; | ||
public LocalDate access; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For all other timestamps in the API, we have returned an Instant
object, and configured the object mapper to use the jackson JSR310 datatype. We have validated to work across all the supported clients.
IDK how LocalDate
serializes / deserializes to json and if it is portable across the different clients.
I think we should stick to returning Instant
in the REST API unless there is a compelling reason not to
@Transient | ||
private final UUID randomnessSource; | ||
|
||
private final String hash; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need a unique constraint on user & hash? If there is a hash collision of API keys, what happens?
If in the future, we create 2 keys with different authorization levels, could we have 2 keys in the db with the same hash? in that case could we distinguish between the keys?
@Enumerated | ||
public final UserService.KeyType type; | ||
|
||
public LocalDate creation, access; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similar comment as in API about Instant
we probably want to standarize unless compelling reason not to.
public int compareTo(UserApiKey other) { | ||
return Comparator.<UserApiKey, LocalDate> comparing(a -> a.creation).thenComparing(a -> a.id).compare(this, other); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
just out of curiosity, when do we need to sort the API Keys?
public UserService.ApiKeyResponse toResponse() { | ||
UserService.ApiKeyResponse response = new UserService.ApiKeyResponse(); | ||
response.id = id; | ||
response.name = name; | ||
response.type = type; | ||
response.creation = creation; | ||
response.access = access; | ||
response.isRevoked = revoked; | ||
response.toExpiration = toExpiration(LocalDate.now()); | ||
return response; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For all other entities, mapping has been decoupled from the API via Mappers defined in the io.hyperfoil.tools.horreum.mapper
package
// update last access | ||
userKey.access = timeService.today(); | ||
|
||
// create identity with just the principal, roles will be populated in RolesAugmentor |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there strict ordering between ApiKeyIdentityProvider
and RolesAugmentor
?
newKey.persist(); | ||
userInfo.persist(); | ||
|
||
Log.infov("{0} created API key \"{1}\"", getUsername(), request.name == null ? "" : request.name); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this should be debug level
} | ||
String oldName = key.name; | ||
key.name = newName == null ? "" : newName; | ||
Log.infov("{0} renamed API key \"{1}\" to \"{2}\"", getUsername(), oldName, newName == null ? "" : newName); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this should be debug level
UserApiKey key = UserApiKey.<UserApiKey> findByIdOptional(keyId) | ||
.orElseThrow(() -> ServiceException.notFound(format("Key with id {0} not found", keyId))); | ||
key.revoked = true; | ||
Log.infov("{0} revoked API key \"{1}\"", getUsername(), key.name); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this should be debug level
} | ||
// revoke expired keys -- could be done directly in the DB but iterate instead to be able to log | ||
UserApiKey.<UserApiKey> stream("#UserApiKey.pastExpiration", timeService.today()).forEach(key -> { | ||
Log.infov("Revoked idle API key \"{0}\"", key.name); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this should be debug level
as discussed in #1922